From 0c84e53afb7ff1a7671fd2568474c7ef77d69b33 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Mon, 20 Feb 2017 01:22:39 -0500 Subject: [PATCH 1/4] Move markup handling into markup.{c,h} This is strictly a code-organization change, and should contain no functional changes. --- src/markup.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++ src/markup.h | 11 ++++++ src/notification.c | 68 ++--------------------------------- src/notification.h | 2 -- src/x.c | 3 +- 5 files changed, 104 insertions(+), 69 deletions(-) create mode 100644 src/markup.c create mode 100644 src/markup.h diff --git a/src/markup.c b/src/markup.c new file mode 100644 index 0000000..03365be --- /dev/null +++ b/src/markup.c @@ -0,0 +1,89 @@ +/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ + +#define _GNU_SOURCE +#include "markup.h" + +#include +#include + +#include "settings.h" +#include "utils.h" + +/* + * Quote a text string for rendering with pango + */ +static char *markup_quote(char *str) +{ + if (str == NULL) { + return NULL; + } + + str = string_replace_all("&", "&", str); + str = string_replace_all("\"", """, str); + str = string_replace_all("'", "'", str); + str = string_replace_all("<", "<", str); + str = string_replace_all(">", ">", str); + + return str; +} + +/* + * Strip any markup from text + */ +char *markup_strip(char *str) +{ + if (str == NULL) { + return NULL; + } + + /* strip all tags */ + string_strip_delimited(str, '<', '>'); + + /* unquote the remainder */ + str = string_replace_all(""", "\"", str); + str = string_replace_all("'", "'", str); + str = string_replace_all("&", "&", str); + str = string_replace_all("<", "<", str); + str = string_replace_all(">", ">", str); + + return str; +} + +/* + * Transform the string in accordance with `markup_mode` and + * `settings.ignore_newline` + */ +char *markup_transform(char *str, enum markup_mode markup_mode) +{ + if (str == NULL) { + return NULL; + } + + if (markup_mode == MARKUP_NO) { + str = markup_quote(str); + } else { + if (settings.ignore_newline) { + str = string_replace_all("
", " ", str); + str = string_replace_all("
", " ", str); + str = string_replace_all("
", " ", str); + } else { + str = string_replace_all("
", "\n", str); + str = string_replace_all("
", "\n", str); + str = string_replace_all("
", "\n", str); + } + + if (markup_mode != MARKUP_FULL ) { + str = markup_strip(str); + str = markup_quote(str); + } + + } + + if (settings.ignore_newline) { + str = string_replace_all("\n", " ", str); + } + + return str; +} + +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/markup.h b/src/markup.h new file mode 100644 index 0000000..8304e2d --- /dev/null +++ b/src/markup.h @@ -0,0 +1,11 @@ +/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ +#ifndef DUNST_MARKUP_H +#define DUNST_MARKUP_H + +#include "settings.h" + +char *markup_strip(char *str); +char *markup_transform(char *str, enum markup_mode markup_mode); + +#endif +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/notification.c b/src/notification.c index 141fdfe..930a0f8 100644 --- a/src/notification.c +++ b/src/notification.c @@ -16,6 +16,7 @@ #include "dbus.h" #include "dunst.h" +#include "markup.h" #include "menu.h" #include "rules.h" #include "settings.h" @@ -196,46 +197,6 @@ void notification_free(notification * n) g_free(n); } -/* - * Strip any markup from text - */ -char *notification_strip_markup(char *str) -{ - if (str == NULL) { - return NULL; - } - - /* strip all tags */ - string_strip_delimited(str, '<', '>'); - - /* unquote the remainder */ - str = string_replace_all(""", "\"", str); - str = string_replace_all("'", "'", str); - str = string_replace_all("&", "&", str); - str = string_replace_all("<", "<", str); - str = string_replace_all(">", ">", str); - - return str; -} - -/* - * Quote a text string for rendering with pango - */ -char *notification_quote_markup(char *str) -{ - if (str == NULL) { - return NULL; - } - - str = string_replace_all("&", "&", str); - str = string_replace_all("\"", """, str); - str = string_replace_all("'", "'", str); - str = string_replace_all("<", "<", str); - str = string_replace_all(">", ">", str); - - return str; -} - /* * Replace all occurrences of "needle" with a quoted "replacement", * according to the markup settings. @@ -245,32 +206,7 @@ char *notification_replace_format(const char *needle, const char *replacement, char* tmp; char* ret; - if (markup_mode == MARKUP_NO) { - tmp = g_strdup(replacement); - tmp = notification_quote_markup(tmp); - } else { - tmp = g_strdup(replacement); - if (settings.ignore_newline) { - tmp = string_replace_all("
", " ", tmp); - tmp = string_replace_all("
", " ", tmp); - tmp = string_replace_all("
", " ", tmp); - } else { - tmp = string_replace_all("
", "\n", tmp); - tmp = string_replace_all("
", "\n", tmp); - tmp = string_replace_all("
", "\n", tmp); - } - - if (markup_mode != MARKUP_FULL ) { - tmp = notification_strip_markup(tmp); - tmp = notification_quote_markup(tmp); - } - - } - - if (settings.ignore_newline) { - tmp = string_replace_all("\n", " ", tmp); - } - + tmp = markup_transform(g_strdup(replacement), markup_mode); ret = string_replace_all(needle, tmp, haystack); g_free(tmp); diff --git a/src/notification.h b/src/notification.h index 777ce73..5d0fa5d 100644 --- a/src/notification.h +++ b/src/notification.h @@ -69,8 +69,6 @@ int notification_is_duplicate(const notification *a, const notification *b); void notification_run_script(notification * n); int notification_close(notification * n, int reason); void notification_print(notification * n); -char *notification_strip_markup(char *str); -char *notification_quote_markup(char *str); char *notification_replace_format(const char *needle, const char *replacement, char *haystack, enum markup_mode markup); void notification_update_text_to_render(notification *n); int notification_get_ttl(notification *n); diff --git a/src/x.c b/src/x.c index 310c341..dbb074a 100644 --- a/src/x.c +++ b/src/x.c @@ -22,6 +22,7 @@ #include #include "dunst.h" +#include "markup.h" #include "notification.h" #include "settings.h" #include "utils.h" @@ -475,7 +476,7 @@ static colored_layout *r_create_layout_from_notification(cairo_t *c, notificatio pango_layout_set_attributes(cl->l, cl->attr); } else { /* remove markup and display plain message instead */ - n->text_to_render = notification_strip_markup(n->text_to_render); + n->text_to_render = markup_strip(n->text_to_render); cl->text = NULL; cl->attr = NULL; pango_layout_set_text(cl->l, n->text_to_render, -1); From c645ba31062082f1c6ecfe460e73486be131b9f8 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 9 Mar 2017 14:25:33 -0500 Subject: [PATCH 2/4] Add tests for markup functions --- test/markup.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/test.c | 2 ++ 2 files changed, 59 insertions(+) create mode 100644 test/markup.c diff --git a/test/markup.c b/test/markup.c new file mode 100644 index 0000000..cbd8bad --- /dev/null +++ b/test/markup.c @@ -0,0 +1,57 @@ +#include "greatest.h" + +#include +#include + +#include "src/markup.h" + +TEST test_markup_strip(void) +{ + char *ptr; + + ASSERT_STR_EQ(""", (ptr=markup_strip(g_strdup("&quot;")))); + g_free(ptr); + ASSERT_STR_EQ("'", (ptr=markup_strip(g_strdup("&apos;")))); + g_free(ptr); + ASSERT_STR_EQ("<", (ptr=markup_strip(g_strdup("&lt;")))); + g_free(ptr); + ASSERT_STR_EQ(">", (ptr=markup_strip(g_strdup("&gt;")))); + g_free(ptr); + ASSERT_STR_EQ("&", (ptr=markup_strip(g_strdup("&amp;")))); + g_free(ptr); + ASSERT_STR_EQ(">A ", (ptr=markup_strip(g_strdup(">A foo
bar\nbaz"), MARKUP_NO))); + g_free(ptr); + ASSERT_STR_EQ("foo\nbar\nbaz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_STRIP))); + g_free(ptr); + ASSERT_STR_EQ("foo\nbar\nbaz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_FULL))); + g_free(ptr); + + settings.ignore_newline = true; + ASSERT_STR_EQ("<i>foo</i><br>bar baz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_NO))); + g_free(ptr); + ASSERT_STR_EQ("foo bar baz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_STRIP))); + g_free(ptr); + ASSERT_STR_EQ("foo bar baz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_FULL))); + g_free(ptr); + + PASS(); +} + +SUITE(suite_markup) +{ + RUN_TEST(test_markup_strip); + RUN_TEST(test_markup_transform); +} + +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/test/test.c b/test/test.c index 417ba19..90959a6 100644 --- a/test/test.c +++ b/test/test.c @@ -3,6 +3,7 @@ SUITE_EXTERN(suite_utils); SUITE_EXTERN(suite_option_parser); SUITE_EXTERN(suite_notification); +SUITE_EXTERN(suite_markup); GREATEST_MAIN_DEFS(); @@ -11,6 +12,7 @@ int main(int argc, char *argv[]) { RUN_SUITE(suite_utils); RUN_SUITE(suite_option_parser); RUN_SUITE(suite_notification); + RUN_SUITE(suite_markup); GREATEST_MAIN_END(); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ From 18c4b4bf7aaefc570a8fde1f8621315821fd3cfa Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 9 Mar 2017 11:33:06 -0500 Subject: [PATCH 3/4] markup.c: Tidy, expand comments This turns a hard-to-understand nested if{} chain into a simple switch statement, and pulls some code out in to utility functions. This is strictly a code-organization change, and should contain no functional changes. --- src/markup.c | 79 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/markup.c b/src/markup.c index 03365be..6ff1014 100644 --- a/src/markup.c +++ b/src/markup.c @@ -9,14 +9,9 @@ #include "settings.h" #include "utils.h" -/* - * Quote a text string for rendering with pango - */ static char *markup_quote(char *str) { - if (str == NULL) { - return NULL; - } + assert(str != NULL); str = string_replace_all("&", "&", str); str = string_replace_all("\"", """, str); @@ -27,8 +22,40 @@ static char *markup_quote(char *str) return str; } +static char *markup_unquote(char *str) +{ + assert(str != NULL); + + str = string_replace_all(""", "\"", str); + str = string_replace_all("'", "'", str); + str = string_replace_all("&", "&", str); + str = string_replace_all("<", "<", str); + str = string_replace_all(">", ">", str); + + return str; +} + +static char *markup_br2nl(char *str) +{ + assert(str != NULL); + + str = string_replace_all("
", "\n", str); + str = string_replace_all("
", "\n", str); + str = string_replace_all("
", "\n", str); + return str; +} + /* - * Strip any markup from text + * Strip any markup from text; turn it in to plain text. + * + * For well-formed markup, the following two commands should be + * roughly equivalent: + * + * out = markup_strip(in); + * pango_parse_markup(in, -1, 0, NULL, &out, NULL, NULL); + * + * However, `pango_parse_markup()` balks at invalid markup; + * `markup_strip()` shouldn't care if there is invalid markup. */ char *markup_strip(char *str) { @@ -40,11 +67,7 @@ char *markup_strip(char *str) string_strip_delimited(str, '<', '>'); /* unquote the remainder */ - str = string_replace_all(""", "\"", str); - str = string_replace_all("'", "'", str); - str = string_replace_all("&", "&", str); - str = string_replace_all("<", "<", str); - str = string_replace_all(">", ">", str); + str = markup_unquote(str); return str; } @@ -59,24 +82,22 @@ char *markup_transform(char *str, enum markup_mode markup_mode) return NULL; } - if (markup_mode == MARKUP_NO) { + switch (markup_mode) { + case MARKUP_NULL: + /* `assert(false)`, but with a meaningful error message */ + assert(markup_mode != MARKUP_NULL); + break; + case MARKUP_NO: str = markup_quote(str); - } else { - if (settings.ignore_newline) { - str = string_replace_all("
", " ", str); - str = string_replace_all("
", " ", str); - str = string_replace_all("
", " ", str); - } else { - str = string_replace_all("
", "\n", str); - str = string_replace_all("
", "\n", str); - str = string_replace_all("
", "\n", str); - } - - if (markup_mode != MARKUP_FULL ) { - str = markup_strip(str); - str = markup_quote(str); - } - + break; + case MARKUP_STRIP: + str = markup_br2nl(str); + str = markup_strip(str); + str = markup_quote(str); + break; + case MARKUP_FULL: + str = markup_br2nl(str); + break; } if (settings.ignore_newline) { From 1f77b286910fdd8573fb72b2a45e5a5498342f3b Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 8 Mar 2017 14:37:14 -0500 Subject: [PATCH 4/4] markup.c: markup_unquote(): Unquote things in the correct order Because "&" is not the last character to be unescaped, it is possible that the lines for "<" and ">" expand some things they shouldn't. For example, "&lt;" should become "<", but instead it becomes ">". While this is unlikely to appear naturally in a notification, it is wrong. --- src/markup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/markup.c b/src/markup.c index 6ff1014..05ef1ff 100644 --- a/src/markup.c +++ b/src/markup.c @@ -28,9 +28,9 @@ static char *markup_unquote(char *str) str = string_replace_all(""", "\"", str); str = string_replace_all("'", "'", str); - str = string_replace_all("&", "&", str); str = string_replace_all("<", "<", str); str = string_replace_all(">", ">", str); + str = string_replace_all("&", "&", str); return str; }