From 5ed65c8733b5fa4a8efc49d75aef631ce841013d Mon Sep 17 00:00:00 2001 From: Witold Filipczyk Date: Thu, 1 Jul 2021 20:18:29 +0200 Subject: [PATCH] [gemini] text/gemini --- src/document/Makefile | 2 +- src/document/gemini/Makefile | 6 + src/document/gemini/meson.build | 1 + src/document/gemini/renderer.c | 212 +++++++++++++++++++++++++ src/document/gemini/renderer.h | 18 +++ src/document/meson.build | 1 + src/document/renderer.c | 5 + src/ecmascript/spidermonkey/document.c | 58 ------- src/mime/backend/default.c | 1 + src/session/download.c | 1 + src/util/string.c | 57 +++++++ src/util/string.h | 2 + 12 files changed, 305 insertions(+), 59 deletions(-) create mode 100644 src/document/gemini/Makefile create mode 100644 src/document/gemini/meson.build create mode 100644 src/document/gemini/renderer.c create mode 100644 src/document/gemini/renderer.h diff --git a/src/document/Makefile b/src/document/Makefile index 8ea77ed28..8078075f8 100644 --- a/src/document/Makefile +++ b/src/document/Makefile @@ -4,7 +4,7 @@ include $(top_builddir)/Makefile.config SUBDIRS-$(CONFIG_CSS) += css SUBDIRS-$(CONFIG_DOM) += dom -SUBDIRS = html plain +SUBDIRS = gemini html plain OBJS = docdata.o document.o format.o forms.o options.o refresh.o renderer.o diff --git a/src/document/gemini/Makefile b/src/document/gemini/Makefile new file mode 100644 index 000000000..21f56d72c --- /dev/null +++ b/src/document/gemini/Makefile @@ -0,0 +1,6 @@ +top_builddir=../../.. +include $(top_builddir)/Makefile.config + +OBJS = renderer.o + +include $(top_srcdir)/Makefile.lib diff --git a/src/document/gemini/meson.build b/src/document/gemini/meson.build new file mode 100644 index 000000000..6a2e1242d --- /dev/null +++ b/src/document/gemini/meson.build @@ -0,0 +1 @@ +srcs += files('renderer.c') diff --git a/src/document/gemini/renderer.c b/src/document/gemini/renderer.c new file mode 100644 index 000000000..37985b595 --- /dev/null +++ b/src/document/gemini/renderer.c @@ -0,0 +1,212 @@ +/* Plain text document renderer */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "elinks.h" + +#include "bookmarks/bookmarks.h" +#include "cache/cache.h" +#include "config/options.h" +#include "document/docdata.h" +#include "document/document.h" +#include "document/format.h" +#include "document/options.h" +#include "document/gemini/renderer.h" +#include "document/html/renderer.h" +#include "document/renderer.h" +#include "globhist/globhist.h" +#include "intl/charsets.h" +#include "protocol/protocol.h" +#include "protocol/uri.h" +#include "terminal/color.h" +#include "terminal/draw.h" +#include "util/color.h" +#include "util/error.h" +#include "util/memory.h" +#include "util/string.h" + +#include + +static void +convert_single_line(struct string *ret, struct string *line) +{ + if (line->length >= 4 && !strncmp(line->source, "### ", 4)) { + add_to_string(ret, "

"); + add_bytes_to_string(ret, line->source + 4, line->length - 4); + add_to_string(ret, "

"); + return; + } + + if (line->length >= 3 && !strncmp(line->source, "## ", 3)) { + add_to_string(ret, "

"); + add_bytes_to_string(ret, line->source + 3, line->length - 3); + add_to_string(ret, "

"); + return; + } + + if (line->length >= 2 && !strncmp(line->source, "# ", 2)) { + add_to_string(ret, "

"); + add_bytes_to_string(ret, line->source + 2, line->length - 2); + add_to_string(ret, "

"); + return; + } + + if (line->length >= 2 && !strncmp(line->source, "* ", 2)) { + add_to_string(ret, "
  • "); + add_bytes_to_string(ret, line->source + 2, line->length - 2); + add_to_string(ret, "
  • "); + return; + } + + if (line->length >= 2 && !strncmp(line->source, "> ", 2)) { + add_to_string(ret, "
    "); + add_bytes_to_string(ret, line->source + 2, line->length - 2); + add_to_string(ret, "
    "); + return; + } + + if (line->length >= 2 && !strncmp(line->source, "=>", 2)) { + int i = 2; + int begin; + add_to_string(ret, "length; ++i) { + if (line->source[i] != ' ' && line->source[i] != '\t') { + break; + }; + } + begin = i; + + for (; i < line->length; ++i) { + if (line->source[i] == ' ' || line->source[i] == '\t') { + break; + } + } + + add_bytes_to_string(ret, line->source + begin, i - begin); + add_to_string(ret, "\">"); + + for (; i < line->length; ++i) { + if (line->source[i] != ' ' && line->source[i] != '\t') { + break; + }; + } + + add_bytes_to_string(ret, line->source + i, line->length - i); + add_to_string(ret, ""); + return; + } + + add_string_to_string(ret, line); + add_to_string(ret, "
    "); +} +/* + r"^# (.*)": "h1", + r"^## (.*)": "h2", + r"^### (.*)": "h3", + r"^\* (.*)": "li", + r"^> (.*)": "blockquote", + r"^=>\s*(\S+)(\s+.*)?": "a" +*/ +/* +def convert_single_line(gmi_line): + for pattern in tags_dict.keys(): + if match := re.match(pattern, gmi_line): + tag = tags_dict[pattern] + groups = match.groups() + if tag == "a": + href = groups[0] + inner_text = groups[1].strip() if len(groups) > 1 else href + return f"<{tag} href='{href}'>{inner_text}" + else: + inner_text = groups[0].strip() + return f"<{tag}>{inner_text}" + return f"

    {gmi_line}

    " +*/ + +void +render_gemini_document(struct cache_entry *cached, struct document *document, + struct string *buffer) +{ + int preformat = 0; + int in_list = 0; + int i = 0; + int begin = 0; + struct string pre_start = INIT_STRING("
    ", 5);
    +	struct string pre_end = INIT_STRING("
    ", 6); + struct string gem_pre = INIT_STRING("```", 3); + struct string html; + char *uristring; + + char *head = empty_string_or_(cached->head); + + (void)get_convert_table(head, document->options.cp, + document->options.assume_cp, + &document->cp, + &document->cp_status, + document->options.hard_assume); + + init_string(&html); + uristring = get_uri_string(document->uri, URI_PUBLIC); + + add_to_string(&html, ""); + mem_free_if(uristring); + + while ( i < buffer->length) { + + for (i = begin; i < buffer->length; ++i) { + if (buffer->source[i] == 13 || buffer->source[i] == 10) break; + } + + if (begin < i) { + int len = i - begin; + + struct string line; + line.source = buffer->source + begin; + line.length = len; + struct string *repl; + + if (len >= 3 && (line.source[0] == '`' && line.source[1] == '`' && line.source[2] == '`') + || (line.source[len-1] == '`' && line.source[len-2] == '`' && line.source[len-3])) { + preformat = !preformat; + repl = preformat ? &pre_start : &pre_end; + string_replace(&html, &line, &gem_pre, repl); + } else if (preformat) { + add_string_to_string(&html, &line); + } else { + struct string html_line; + + init_string(&html_line); + convert_single_line(&html_line, &line); + + if (html_line.length >= 4 + && !strcmp(html_line.source, "
  • ")) { + if (!in_list) { + in_list = 1; + add_to_string(&html, "
      \n"); + add_string_to_string(&html, &html_line); + } + } else if (in_list) { + in_list = 0; + add_to_string(&html, "
    \n"); + add_string_to_string(&html, &html_line); + } else { + add_string_to_string(&html, &html_line); + } + done_string(&html_line); + } + } + begin = i + 1; + add_to_string(&html, "\n"); + } + add_to_string(&html, ""); + + render_html_document(cached, document, &html); +} diff --git a/src/document/gemini/renderer.h b/src/document/gemini/renderer.h new file mode 100644 index 000000000..7215d633f --- /dev/null +++ b/src/document/gemini/renderer.h @@ -0,0 +1,18 @@ +#ifndef EL__DOCUMENT_GEMINI_RENDERER_H +#define EL__DOCUMENT_GEMINI_RENDERER_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct cache_entry; +struct document; +struct string; + +void render_gemini_document(struct cache_entry *cached, struct document *document, struct string *buffer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/document/meson.build b/src/document/meson.build index 8873aa5f2..cd3d03972 100644 --- a/src/document/meson.build +++ b/src/document/meson.build @@ -4,6 +4,7 @@ endif if conf_data.get('CONFIG_DOM') subdir('dom') endif +subdir('gemini') subdir('html') subdir('plain') if conf_data.get('CONFIG_XML') diff --git a/src/document/renderer.c b/src/document/renderer.c index c489b8784..cf3ac35b2 100644 --- a/src/document/renderer.c +++ b/src/document/renderer.c @@ -16,6 +16,7 @@ #include "config/options.h" #include "document/document.h" #include "document/dom/renderer.h" +#include "document/gemini/renderer.h" #include "document/html/frames.h" #include "document/html/renderer.h" #include "document/plain/renderer.h" @@ -282,6 +283,10 @@ render_encoded_document(struct cache_entry *cached, struct document *document) render_dom_document(cached, document, &buffer); else #endif + if (cached->content_type + && (!c_strlcasecmp("text/gemini", 11, cached->content_type, -1))) + render_gemini_document(cached, document, &buffer); + else #ifdef CONFIG_XML if (true) render_xhtml_document(cached, document, NULL); else diff --git a/src/ecmascript/spidermonkey/document.c b/src/ecmascript/spidermonkey/document.c index 15421a33e..f71d029ad 100644 --- a/src/ecmascript/spidermonkey/document.c +++ b/src/ecmascript/spidermonkey/document.c @@ -1135,64 +1135,6 @@ document_writeln(JSContext *ctx, unsigned int argc, JS::Value *rval) return document_write_do(ctx, argc, rval, 1); } -void -string_replace(struct string *res, struct string *inp, struct string *what, struct string *repl) -{ - struct string tmp; - struct string tmp2; - char *head; - char *found; - char *ins; - char *tmp_cnt; - - init_string(&tmp); - init_string(&tmp2); - - add_string_to_string(&tmp, inp); - - head = tmp.source; - int count = 0; - ins = head; - if (what->length==0) - { - add_string_to_string(res, inp); - return; - } - - // count occurence of string in input - for (count = 0; tmp_cnt = strstr(ins, what->source); ++count) - { - ins = tmp_cnt + what->length; - } - - for (int i=0;isource); - // count chars before and after occurence - int bf_len=found-tmp.source; - int af_len=tmp.length-bf_len-what->length; - // move head by what - found+=what->length; - // join the before, needle and after to res - add_bytes_to_string(&tmp2,tmp.source,bf_len); - add_bytes_to_string(&tmp2,repl->source,repl->length); - add_bytes_to_string(&tmp2,found,af_len); - // clear tmp string and tmp2 string - done_string(&tmp); - init_string(&tmp); - add_string_to_string(&tmp, &tmp2); - done_string(&tmp2); - init_string(&tmp2); - //printf("TMP: %s |\n",tmp.source); - head = tmp.source; - } - add_string_to_string(res, &tmp); - - done_string(&tmp); - done_string(&tmp2); - -} - /* @document_funcs{"replace"} */ static bool document_replace(JSContext *ctx, unsigned int argc, JS::Value *vp) diff --git a/src/mime/backend/default.c b/src/mime/backend/default.c index 038f6607f..0093d8ec9 100644 --- a/src/mime/backend/default.c +++ b/src/mime/backend/default.c @@ -98,6 +98,7 @@ static union option_info default_mime_options[] = { INIT_OPT_MIME_EXTENSION("txt", "text/plain"), INIT_OPT_MIME_EXTENSION("htm", "text/html"), INIT_OPT_MIME_EXTENSION("html", "text/html"), + INIT_OPT_MIME_EXTENSION("gmi", "text/gemini"), #ifdef CONFIG_BITTORRENT INIT_OPT_MIME_EXTENSION("torrent", "application/x-bittorrent"), #endif diff --git a/src/session/download.c b/src/session/download.c index d4efa348d..9f2666f24 100644 --- a/src/session/download.c +++ b/src/session/download.c @@ -1714,6 +1714,7 @@ struct { } static const known_types[] = { { "text/html", 0 }, { "text/plain", 1 }, + { "text/gemini", 0 }, { "application/xhtml+xml", 0 }, /* RFC 3236 */ #if CONFIG_DOM { "application/docbook+xml", 1 }, diff --git a/src/util/string.c b/src/util/string.c index 054407e93..891d219cf 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -544,6 +544,63 @@ add_format_to_string(struct string *string, const char *format, ...) return string; } +void +string_replace(struct string *res, struct string *inp, struct string *what, struct string *repl) +{ + struct string tmp; + struct string tmp2; + char *head; + char *found; + char *ins; + char *tmp_cnt; + + init_string(&tmp); + init_string(&tmp2); + + add_string_to_string(&tmp, inp); + + head = tmp.source; + int count = 0; + ins = head; + if (what->length==0) + { + add_string_to_string(res, inp); + return; + } + + // count occurence of string in input + for (count = 0; tmp_cnt = strstr(ins, what->source); ++count) + { + ins = tmp_cnt + what->length; + } + + for (int i=0;isource); + // count chars before and after occurence + int bf_len=found-tmp.source; + int af_len=tmp.length-bf_len-what->length; + // move head by what + found+=what->length; + // join the before, needle and after to res + add_bytes_to_string(&tmp2,tmp.source,bf_len); + add_bytes_to_string(&tmp2,repl->source,repl->length); + add_bytes_to_string(&tmp2,found,af_len); + // clear tmp string and tmp2 string + done_string(&tmp); + init_string(&tmp); + add_string_to_string(&tmp, &tmp2); + done_string(&tmp2); + init_string(&tmp2); + //printf("TMP: %s |\n",tmp.source); + head = tmp.source; + } + add_string_to_string(res, &tmp); + + done_string(&tmp); + done_string(&tmp2); +} + struct string * add_to_string_list(LIST_OF(struct string_list_item) *list, const char *source, int length) diff --git a/src/util/string.h b/src/util/string.h index 213a00c3a..d3965f592 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -294,6 +294,8 @@ add_to_string_list(LIST_OF(struct string_list_item) *list, void free_string_list(LIST_OF(struct string_list_item) *list); +void string_replace(struct string *res, struct string *inp, struct string *what, struct string *repl); + /** Returns an empty C string or @a str if different from NULL. */ #define empty_string_or_(str) ((str) ? (char *) (str) : (char *) "")