180 lines
3.8 KiB
D
180 lines
3.8 KiB
D
module converter;
|
|
|
|
import std.array, std.algorithm, std.string, std.format;
|
|
|
|
import parser;
|
|
|
|
enum Format {
|
|
none, html, gemtext, ast
|
|
}
|
|
|
|
interface Converter {
|
|
string convert(Tree md);
|
|
@property string extension();
|
|
static Converter format(Format f) {
|
|
final switch(f) {
|
|
case Format.html:
|
|
return HTMLConverter.instance;
|
|
case Format.gemtext:
|
|
return GemtextConverter.instance;
|
|
case Format.ast:
|
|
return ASTConverter.instance;
|
|
case Format.none:
|
|
throw new Exception("Invalid format type.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// escapes links
|
|
private string escapeLink(string link) {
|
|
static const string linkSpecialChars = ` !"#$%'()*+,-;<=>?@\`;
|
|
auto ap = appender!string;
|
|
foreach(i, c; link) {
|
|
if(linkSpecialChars.canFind(c))
|
|
ap ~= "%%%02x".format(c);
|
|
else
|
|
ap ~= c;
|
|
}
|
|
return ap[];
|
|
}
|
|
|
|
private class HTMLConverter : Converter {
|
|
private {
|
|
static string[dchar] escapeTable;
|
|
// converts html special chars to html entities
|
|
string escape(string s) {
|
|
return s.translate(escapeTable);
|
|
}
|
|
}
|
|
|
|
string convert(Tree md) {
|
|
final switch(md.type) {
|
|
case TreeType.top: {
|
|
auto ap = appender!string;
|
|
ap ~= "<html><head></head><body>";
|
|
foreach(tree; md.children)
|
|
ap ~= convert(tree);
|
|
ap ~= "</body></html>";
|
|
return ap[];
|
|
}
|
|
case TreeType.line: {
|
|
auto ap = appender!string;
|
|
ap ~= "<p>";
|
|
foreach(tree; md.children)
|
|
ap ~= convert(tree);
|
|
ap ~= "</p>";
|
|
return ap[];
|
|
}
|
|
case TreeType.text:
|
|
return escape(md.text);
|
|
case TreeType.bold:
|
|
return "<b>"~escape(md.text)~"</b>";
|
|
case TreeType.italic:
|
|
return "<i>"~escape(md.text)~"</i>";
|
|
case TreeType.link:
|
|
return `<a href="`~escapeLink(md.link.link)~`">`~escape(md.link.label)~`</a>`;
|
|
case TreeType.header: {
|
|
auto ap = appender!string;
|
|
ap ~= "<h%u>".format(md.header.size);
|
|
foreach(tree; md.header.children)
|
|
ap ~= convert(tree);
|
|
ap ~= "</h%u>".format(md.header.size);
|
|
return ap[];
|
|
}
|
|
case TreeType.list: {
|
|
auto ap = appender!string;
|
|
ap ~= "<ul>";
|
|
foreach(tree; md.children)
|
|
ap ~= "<li>"~convert(tree)~"</li>";
|
|
ap ~= "</ul>";
|
|
return ap[];
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
@property string extension() { return "html"; }
|
|
|
|
static Converter instance;
|
|
|
|
static this() {
|
|
escapeTable = [
|
|
'&': "&",
|
|
'<': "<",
|
|
'>': ">",
|
|
'"': """
|
|
];
|
|
instance = new HTMLConverter;
|
|
}
|
|
|
|
}
|
|
|
|
private class GemtextConverter : Converter {
|
|
string convert(Tree md) {
|
|
final switch(md.type) {
|
|
case TreeType.top: {
|
|
auto ap = appender!string;
|
|
foreach(tree; md.children)
|
|
ap ~= convert(tree);
|
|
ap ~= "\n";
|
|
return ap[];
|
|
}
|
|
case TreeType.line: {
|
|
auto ap = appender!string;
|
|
foreach(tree; md.children)
|
|
ap ~= convert(tree);
|
|
ap ~= '\n';
|
|
return ap[];
|
|
}
|
|
case TreeType.text:
|
|
return md.text;
|
|
case TreeType.italic:
|
|
return "/"~md.text~"/";
|
|
case TreeType.bold:
|
|
return "*"~md.text~"*";
|
|
case TreeType.link:
|
|
return "\n=> "~escapeLink(md.link.link)~" "~md.link.label~"\n";
|
|
case TreeType.header: {
|
|
auto ap = appender!string;
|
|
auto h = md.header;
|
|
if(h.size > 3)
|
|
ap ~= '[';
|
|
else {
|
|
for(uint i = 0; i < h.size; i++)
|
|
ap ~= '#';
|
|
ap ~= ' ';
|
|
}
|
|
foreach(tree; md.header.children)
|
|
ap ~= convert(tree);
|
|
if(h.size > 3)
|
|
ap ~= ']';
|
|
ap ~= '\n';
|
|
return ap[];
|
|
}
|
|
case TreeType.list: {
|
|
auto ap = appender!string;
|
|
foreach(tree; md.children)
|
|
ap ~= "* "~convert(tree);
|
|
ap ~= "\n";
|
|
return ap[];
|
|
}
|
|
}
|
|
}
|
|
|
|
@property string extension() { return "gmi"; }
|
|
|
|
static Converter instance;
|
|
|
|
static this() {
|
|
instance = new GemtextConverter;
|
|
}
|
|
}
|
|
|
|
// not even really a converter
|
|
class ASTConverter : Converter {
|
|
string convert(Tree md) { return md.toString(); }
|
|
@property string extension() { return "txt"; }
|
|
static Converter instance;
|
|
static this() { instance = new ASTConverter; }
|
|
}
|