From 2f4c3d8a6dee2ffc06471dd4de80934c4a3387b1 Mon Sep 17 00:00:00 2001 From: Owen Taylor Date: Fri, 12 Sep 2003 00:17:02 +0000 Subject: [PATCH] Add g_markup_printf_escaped(), g_markup_vprintf_escaped(). Thu Sep 11 20:11:05 2003 Owen Taylor * glib/gmarkup.c: Add g_markup_printf_escaped(), g_markup_vprintf_escaped(). * tests/markup-escape-test.c (main): Test for g_markup_escape_text(), g_markup_printf_escaped(). --- ChangeLog | 8 + ChangeLog.pre-2-10 | 8 + ChangeLog.pre-2-12 | 8 + ChangeLog.pre-2-4 | 8 + ChangeLog.pre-2-6 | 8 + ChangeLog.pre-2-8 | 8 + docs/reference/glib/Makefile.am | 2 +- docs/reference/glib/glib-sections.txt | 2 + docs/reference/glib/tmpl/fileutils.sgml | 10 + docs/reference/glib/tmpl/macros_misc.sgml | 2 + docs/reference/glib/tmpl/markup.sgml | 20 ++ docs/reference/glib/tmpl/misc_utils.sgml | 2 +- docs/reference/glib/tmpl/unicode.sgml | 2 + glib/gmarkup.c | 305 +++++++++++++++++++++++++++++- glib/gmarkup.h | 7 + tests/Makefile.am | 2 + tests/markup-escape-test.c | 95 ++++++++++ 17 files changed, 494 insertions(+), 3 deletions(-) create mode 100644 tests/markup-escape-test.c diff --git a/ChangeLog b/ChangeLog index 05b2788..9b045eb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +Thu Sep 11 20:11:05 2003 Owen Taylor + + * glib/gmarkup.c: Add g_markup_printf_escaped(), + g_markup_vprintf_escaped(). + + * tests/markup-escape-test.c (main): Test for + g_markup_escape_text(), g_markup_printf_escaped(). + 2003-09-10 Noah Levitt * glib/gunicodeprivate.h: diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 05b2788..9b045eb 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,11 @@ +Thu Sep 11 20:11:05 2003 Owen Taylor + + * glib/gmarkup.c: Add g_markup_printf_escaped(), + g_markup_vprintf_escaped(). + + * tests/markup-escape-test.c (main): Test for + g_markup_escape_text(), g_markup_printf_escaped(). + 2003-09-10 Noah Levitt * glib/gunicodeprivate.h: diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12 index 05b2788..9b045eb 100644 --- a/ChangeLog.pre-2-12 +++ b/ChangeLog.pre-2-12 @@ -1,3 +1,11 @@ +Thu Sep 11 20:11:05 2003 Owen Taylor + + * glib/gmarkup.c: Add g_markup_printf_escaped(), + g_markup_vprintf_escaped(). + + * tests/markup-escape-test.c (main): Test for + g_markup_escape_text(), g_markup_printf_escaped(). + 2003-09-10 Noah Levitt * glib/gunicodeprivate.h: diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4 index 05b2788..9b045eb 100644 --- a/ChangeLog.pre-2-4 +++ b/ChangeLog.pre-2-4 @@ -1,3 +1,11 @@ +Thu Sep 11 20:11:05 2003 Owen Taylor + + * glib/gmarkup.c: Add g_markup_printf_escaped(), + g_markup_vprintf_escaped(). + + * tests/markup-escape-test.c (main): Test for + g_markup_escape_text(), g_markup_printf_escaped(). + 2003-09-10 Noah Levitt * glib/gunicodeprivate.h: diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 05b2788..9b045eb 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,11 @@ +Thu Sep 11 20:11:05 2003 Owen Taylor + + * glib/gmarkup.c: Add g_markup_printf_escaped(), + g_markup_vprintf_escaped(). + + * tests/markup-escape-test.c (main): Test for + g_markup_escape_text(), g_markup_printf_escaped(). + 2003-09-10 Noah Levitt * glib/gunicodeprivate.h: diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 05b2788..9b045eb 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,11 @@ +Thu Sep 11 20:11:05 2003 Owen Taylor + + * glib/gmarkup.c: Add g_markup_printf_escaped(), + g_markup_vprintf_escaped(). + + * tests/markup-escape-test.c (main): Test for + g_markup_escape_text(), g_markup_printf_escaped(). + 2003-09-10 Noah Levitt * glib/gunicodeprivate.h: diff --git a/docs/reference/glib/Makefile.am b/docs/reference/glib/Makefile.am index 4f0244f..3bd7e98 100644 --- a/docs/reference/glib/Makefile.am +++ b/docs/reference/glib/Makefile.am @@ -32,7 +32,7 @@ IGNORE_HFILES= \ glibconfig-sysdefs.h \ gdebug.h \ gprintfint.h \ - trio + gnulib # Extra options to supply to gtkdoc-mkdb MKDB_OPTIONS=--sgml-mode --output-format=xml --ignore-files=trio diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 1873567..7c9fd8a 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -843,6 +843,8 @@ GMarkupParseFlags GMarkupParseContext GMarkupParser g_markup_escape_text +g_markup_printf_escaped +g_markup_vprintf_escaped g_markup_parse_context_end_parse g_markup_parse_context_free g_markup_parse_context_get_position diff --git a/docs/reference/glib/tmpl/fileutils.sgml b/docs/reference/glib/tmpl/fileutils.sgml index c9ae8e8..d303782 100644 --- a/docs/reference/glib/tmpl/fileutils.sgml +++ b/docs/reference/glib/tmpl/fileutils.sgml @@ -169,6 +169,16 @@ A test to perform an a file using g_file_test(). @Returns: + + + + + +@filename: +@error: +@Returns: + + An opaque structure representing an opened directory. diff --git a/docs/reference/glib/tmpl/macros_misc.sgml b/docs/reference/glib/tmpl/macros_misc.sgml index d66ff4f..dad8eaa 100644 --- a/docs/reference/glib/tmpl/macros_misc.sgml +++ b/docs/reference/glib/tmpl/macros_misc.sgml @@ -346,6 +346,7 @@ specifiers for values of type #gint32. See also #G_GINT16_MODIFIER. @Since: 2.4 + This is the platform dependent conversion specifier for scanning and @@ -378,6 +379,7 @@ is not defined. @Since: 2.4 + This is the platform dependent conversion specifier for scanning and diff --git a/docs/reference/glib/tmpl/markup.sgml b/docs/reference/glib/tmpl/markup.sgml index d3831a0..c795ee5 100644 --- a/docs/reference/glib/tmpl/markup.sgml +++ b/docs/reference/glib/tmpl/markup.sgml @@ -153,6 +153,26 @@ passthrough text back out in the same position @Returns: + + + + + +@format: +@Varargs: +@Returns: + + + + + + + +@format: +@args: +@Returns: + + diff --git a/docs/reference/glib/tmpl/misc_utils.sgml b/docs/reference/glib/tmpl/misc_utils.sgml index a7b07f2..262d971 100644 --- a/docs/reference/glib/tmpl/misc_utils.sgml +++ b/docs/reference/glib/tmpl/misc_utils.sgml @@ -57,7 +57,7 @@ reasons this function can only be called once. -@variable: +@variable: @Returns: diff --git a/docs/reference/glib/tmpl/unicode.sgml b/docs/reference/glib/tmpl/unicode.sgml index e762a92..5e0fa3f 100644 --- a/docs/reference/glib/tmpl/unicode.sgml +++ b/docs/reference/glib/tmpl/unicode.sgml @@ -304,6 +304,8 @@ See diff --git a/glib/gmarkup.c b/glib/gmarkup.c index ea86cef..0dd265f 100644 --- a/glib/gmarkup.c +++ b/glib/gmarkup.c @@ -1,6 +1,6 @@ /* gmarkup.c - Simple XML-like parser * - * Copyright 2000 Red Hat, Inc. + * Copyright 2000, 2003 Red Hat, Inc. * * GLib is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as @@ -20,6 +20,7 @@ #include "config.h" +#include #include #include #include @@ -1826,3 +1827,305 @@ g_markup_escape_text (const gchar *text, return g_string_free (str, FALSE); } + +/** + * find_conversion: + * @format: a printf-style format string + * @after: location to store a pointer to the character after + * the returned conversion. On a %NULL return, returns the + * pointer to the trailing NUL in the string + * + * Find the next conversion in a printf-style format string. + * Partially based on code from printf-parser.c, + * Copyright (C) 1999-2000, 2002-2003 Free Software Foundation, Inc. + * + * Return value: pointer to the next conversion in @format, + * or %NULL, if none. + **/ +static const char * +find_conversion (const char *format, + const char **after) +{ + const char *start = format; + const char *cp; + + while (*start != '\0' && *start != '%') + start++; + + if (*start == '\0') + { + *after = start; + return NULL; + } + + cp = start + 1; + + if (*cp == '\0') + { + *after = cp; + return NULL; + } + + /* Test for positional argument. */ + if (*cp >= '0' && *cp <= '9') + { + const char *np; + + for (np = cp; *np >= '0' && *np <= '9'; np++) + ; + if (*np == '$') + cp = np + 1; + } + + /* Skip the flags. */ + for (;;) + { + if (*cp == '\'' || + *cp == '-' || + *cp == '+' || + *cp == ' ' || + *cp == '#' || + *cp == '0') + cp++; + else + break; + } + + /* Skip the field width. */ + if (*cp == '*') + { + cp++; + + /* Test for positional argument. */ + if (*cp >= '0' && *cp <= '9') + { + const char *np; + + for (np = cp; *np >= '0' && *np <= '9'; np++) + ; + if (*np == '$') + cp = np + 1; + } + } + else + { + for (; *cp >= '0' && *cp <= '9'; cp++) + ; + } + + /* Skip the precision. */ + if (*cp == '.') + { + cp++; + if (*cp == '*') + { + /* Test for positional argument. */ + if (*cp >= '0' && *cp <= '9') + { + const char *np; + + for (np = cp; *np >= '0' && *np <= '9'; np++) + ; + if (*np == '$') + cp = np + 1; + } + } + else + { + for (; *cp >= '0' && *cp <= '9'; cp++) + ; + } + } + + /* Skip argument type/size specifiers. */ + while (*cp == 'h' || + *cp == 'L' || + *cp == 'l' || + *cp == 'j' || + *cp == 'z' || + *cp == 'Z' || + *cp == 't') + cp++; + + /* Skip the conversion character. */ + cp++; + + *after = cp; + return start; +} + +/** + * g_markup_vprintf_escaped: + * @format: printf() style format string + * @args: variable argument list, similar to vprintf() + * + * Formats the data in @args according to @format, escaping + * all string and character arguments in the fashion + * of g_markup_escape_text(). See g_markup_printf_escaped(). + * + * Return value: newly allocated result from formatting + * operation. Free with g_free(). + **/ +char * +g_markup_vprintf_escaped (const char *format, + va_list args) +{ + GString *format1; + GString *format2; + GString *result = NULL; + gchar *output1 = NULL; + gchar *output2 = NULL; + const char *p, *op1, *op2; + va_list args2; + + /* The technique here, is that we make two format strings that + * have the identical conversions in the identical order to the + * original strings, but differ in the text in-between. We + * then use the normal g_strdup_vprintf() to format the arguments + * with the two new format strings. By comparing the results, + * we can figure out what segments of the output come from + * the the original format string, and what from the arguments, + * and thus know what portions of the string to escape. + * + * For instance, for: + * + * g_markup_printf_escaped ("%s ate %d apples", "Susan & Fred", 5); + * + * We form the two format strings "%sX%dX" and %sY%sY". The results + * of formatting with those two strings are + * + * "%sX%dX" => "Susan & FredX5X" + * "%sY%dY" => "Susan & FredY5Y" + * + * To find the span of the first argument, we find the first position + * where the two arguments differ, which tells us that the first + * argument formatted to "Susan & Fred". We then escape that + * to "Susan & Fred" and join up with the intermediate portions + * of the format string and the second argument to get + * "Susan & Fred ate 5 apples". + */ + + /* Create the two modified format strings + */ + format1 = g_string_new (NULL); + format2 = g_string_new (NULL); + p = format; + while (TRUE) + { + const char *after; + const char *conv = find_conversion (p, &after); + if (!conv) + break; + + g_string_append_len (format1, conv, after - conv); + g_string_append_c (format1, 'X'); + g_string_append_len (format2, conv, after - conv); + g_string_append_c (format2, 'Y'); + + p = after; + } + + /* Use them to format the arguments + */ + G_VA_COPY (args2, args); + + output1 = g_strdup_vprintf (format1->str, args); + va_end (args); + if (!output1) + goto cleanup; + + output2 = g_strdup_vprintf (format2->str, args2); + va_end (args2); + if (!output2) + goto cleanup; + + result = g_string_new (NULL); + + /* Iterate through the original format string again, + * copying the non-conversion portions and the escaped + * converted arguments to the output string. + */ + op1 = output1; + op2 = output2; + p = format; + while (TRUE) + { + const char *after; + const char *output_start; + const char *conv = find_conversion (p, &after); + char *escaped; + + if (!conv) /* The end, after points to the trailing \0 */ + { + g_string_append_len (result, p, after - p); + break; + } + + g_string_append_len (result, p, conv - p); + output_start = op1; + while (*op1 == *op2) + { + op1++; + op2++; + } + + escaped = g_markup_escape_text (output_start, op1 - output_start); + g_string_append (result, escaped); + g_free (escaped); + + p = after; + op1++; + op2++; + } + + cleanup: + g_string_free (format1, TRUE); + g_string_free (format2, TRUE); + g_free (output1); + g_free (output2); + + if (result) + return g_string_free (result, FALSE); + else + return NULL; +} + +/** + * g_markup_printf_escaped: + * @format: printf() style format string + * @Varargs: the arguments to insert in the format string + * + * Formats arguments according to @format, escaping + * all string and character arguments in the fashion + * of g_markup_escape_text(). This is useful when you + * want to insert literal strings into XML-style markup + * output, without having to worry that the strings + * might themselves contain markup. + * + * + * const char *store = "Fortnum & Mason"; + * const char *item = "Tea"; + * char *output; + *   + * output = g_markup_printf_escaped ("<purchase>" + * "<store>%s</store>" + * "<item>%s</item>" + * "</purchase>", + * store, item); + * + * + * Return value: newly allocated result from formatting + * operation. Free with g_free(). + **/ +char * +g_markup_printf_escaped (const char *format, ...) +{ + char *result; + va_list args; + + va_start (args, format); + result = g_markup_vprintf_escaped (format, args); + va_end (args); + + return result; +} diff --git a/glib/gmarkup.h b/glib/gmarkup.h index 5938d70..47e3e87 100644 --- a/glib/gmarkup.h +++ b/glib/gmarkup.h @@ -21,6 +21,8 @@ #ifndef __G_MARKUP_H__ #define __G_MARKUP_H__ +#include + #include G_BEGIN_DECLS @@ -118,6 +120,11 @@ void g_markup_parse_context_get_position (GMarkupParseContext *c gchar* g_markup_escape_text (const gchar *text, gssize length); +gchar *g_markup_printf_escaped (const char *format, + ...) G_GNUC_PRINTF (1, 2); +gchar *g_markup_vprintf_escaped (const char *format, + va_list args); + G_END_DECLS #endif /* __G_MARKUP_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 8015c9d..fe7ac08 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -73,6 +73,7 @@ test_programs = \ iochannel-test \ list-test \ mainloop-test \ + markup-escape-test \ module-test \ node-test \ patterntest \ @@ -121,6 +122,7 @@ iochannel_test_LDADD = $(progs_ldadd) list_test_LDADD = $(progs_ldadd) mainloop_test_LDADD = $(thread_ldadd) markup_test_LDADD = $(progs_ldadd) +markup_escape_test_LDADD = $(progs_ldadd) module_test_LDADD = $(module_ldadd) $(module_test_exp) module_test_LDFLAGS = $(G_MODULE_LDFLAGS) node_test_LDADD = $(progs_ldadd) diff --git a/tests/markup-escape-test.c b/tests/markup-escape-test.c new file mode 100644 index 0000000..667d4dc --- /dev/null +++ b/tests/markup-escape-test.c @@ -0,0 +1,95 @@ +#undef G_DISABLE_ASSERT +#undef G_LOG_DOMAIN + +#include +#include +#include + +static void test_format (const gchar *format, + const gchar *expected, ...) G_GNUC_PRINTF (1, 3); + +static gboolean error = FALSE; + +static void +test (const gchar *original, + const gchar *expected) +{ + gchar *result = g_markup_escape_text (original, -1); + + if (strcmp (result, expected) != 0) + { + g_printerr ("g_markup_escape_text(): expected '%s', got '%s'\n", + expected, result); + error = TRUE; + } + + g_free (result); +} + +static void +test_format (const gchar *format, + const gchar *expected, + ...) +{ + gchar *result; + + va_list args; + + va_start (args, expected); + result = g_markup_vprintf_escaped (format, args); + va_end (args); + + if (strcmp (result, expected) != 0) + { + g_printerr ("g_markup_printf_escaped(): expected '%s', got '%s'\n", + expected, result); + error = TRUE; + } + + g_free (result); +} + +int main (int argc, char **argv) +{ + /* Tests for g_markup_escape_text() */ + test ("&", "&"); + test ("<", "<"); + test (">", ">"); + test ("'", "'"); + test ("\"", """); + + test ("", ""); + test ("A", "A"); + test ("A&", "A&"); + test ("&A", "&A"); + test ("A&A", "A&A"); + test ("&&A", "&&A"); + test ("A&&", "A&&"); + test ("A&&A", "A&&A"); + test ("A&A&A", "A&A&A"); + + /* Tests for g_markup_printf_escaped() */ + test_format ("A", "A"); + test_format ("A%s", "A&", "&"); + test_format ("%sA", "&A", "&"); + test_format ("A%sA", "A&A", "&"); + test_format ("%s%sA", "&&A", "&", "&"); + test_format ("A%s%s", "A&&", "&", "&"); + test_format ("A%s%sA", "A&&A", "&", "&"); + test_format ("A%sA%sA", "A&A&A", "&", "&"); + + test_format ("%s", "<B>&", + "&"); + test_format ("%c%c", "<&", + '<', '&'); + test_format (".%c.%c.", ".<.&.", + '<', '&'); + test_format ("%s", "", + ""); + test_format ("%-5s", "A ", + "A"); + test_format ("%2$s%1$s", "B.A.", + "A.", "B."); + + return error ? 1 : 0; +} -- 2.7.4