aboutsummaryrefslogtreecommitdiffstats
path: root/src/util/format.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util/format.c259
1 files changed, 259 insertions, 0 deletions
diff --git a/src/util/format.c b/src/util/format.c
new file mode 100644
index 000000000..66243c8ec
--- /dev/null
+++ b/src/util/format.c
@@ -0,0 +1,259 @@
+/*
+ * music player command (mpc)
+ * Copyright (C) 2003-2015 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "format.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/**
+ * Reallocate the given string and append the source string.
+ */
+gcc_malloc
+static char *
+string_append(char *dest, const char *src, size_t len)
+{
+ size_t destlen = dest != NULL
+ ? strlen(dest)
+ : 0;
+
+ dest = realloc(dest, destlen + len + 1);
+ memcpy(dest + destlen, src, len);
+ dest[destlen + len] = '\0';
+
+ return dest;
+}
+
+/**
+ * Skip the format string until the current group is closed by either
+ * '&', '|' or ']' (supports nesting).
+ */
+gcc_pure
+static const char *
+skip_format(const char *p)
+{
+ unsigned stack = 0;
+
+ while (*p != '\0') {
+ if (*p == '[')
+ stack++;
+ else if (*p == '#' && p[1] != '\0')
+ /* skip escaped stuff */
+ ++p;
+ else if (stack > 0) {
+ if (*p == ']')
+ --stack;
+ } else if (*p == '&' || *p == '|' || *p == ']')
+ break;
+
+ ++p;
+ }
+
+ return p;
+}
+
+static bool
+is_name_char(char ch)
+{
+ return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
+ (ch >= '0' && ch <= '9') || ch == '_';
+}
+
+static char *
+format_object2(const char *format, const char **last, const void *object,
+ const char *(*getter)(const void *object, const char *name))
+{
+ char *ret = NULL;
+ const char *p;
+ bool found = false;
+
+ for (p = format; *p != '\0';) {
+ switch (p[0]) {
+ case '|':
+ ++p;
+ if (!found) {
+ /* nothing found yet: try the next
+ section */
+ free(ret);
+ ret = NULL;
+ } else
+ /* already found a value: skip the
+ next section */
+ p = skip_format(p);
+ break;
+
+ case '&':
+ ++p;
+ if (!found)
+ /* nothing found yet, so skip this
+ section */
+ p = skip_format(p);
+ else
+ /* we found something yet, but it will
+ only be used if the next section
+ also found something, so reset the
+ flag */
+ found = false;
+ break;
+
+ case '[': {
+ char *t = format_object2(p + 1, &p, object, getter);
+ if (t != NULL) {
+ ret = string_append(ret, t, strlen(t));
+ free(t);
+ found = true;
+ }
+ }
+ break;
+
+ case ']':
+ if (last != NULL)
+ *last = p + 1;
+ if (!found) {
+ free(ret);
+ ret = NULL;
+ }
+ return ret;
+
+ case '\\': {
+ /* take care of escape sequences */
+ char ltemp;
+ switch (p[1]) {
+ case 'a':
+ ltemp = '\a';
+ break;
+
+ case 'b':
+ ltemp = '\b';
+ break;
+
+ case 't':
+ ltemp = '\t';
+ break;
+
+ case 'n':
+ ltemp = '\n';
+ break;
+
+ case 'v':
+ ltemp = '\v';
+ break;
+
+ case 'f':
+ ltemp = '\f';
+ break;
+
+ case 'r':
+ ltemp = '\r';
+ break;
+
+ case '[':
+ case ']':
+ ltemp = p[1];
+ break;
+
+ default:
+ /* unknown escape: copy the
+ backslash */
+ ltemp = p[0];
+ --p;
+ break;
+ }
+
+ ret = string_append(ret, &ltemp, 1);
+ p += 2;
+ }
+ break;
+
+ case '%': {
+ /* find the extent of this format specifier
+ (stop at \0, ' ', or esc) */
+ const char *end = p + 1;
+ while (is_name_char(*end))
+ ++end;
+
+ const size_t length = end - p + 1;
+
+ if (*end != '%') {
+ ret = string_append(ret, p, length - 1);
+ p = end;
+ continue;
+ }
+
+ char name[32];
+ if (length > (int)sizeof(name)) {
+ ret = string_append(ret, p, length);
+ p = end + 1;
+ continue;
+ }
+
+ memcpy(name, p + 1, length - 2);
+ name[length - 2] = 0;
+
+ const char *value = getter(object, name);
+ size_t value_length;
+ if (value != NULL) {
+ if (*value != 0)
+ found = true;
+ value_length = strlen(value);
+ } else {
+ /* unknown variable: copy verbatim
+ from format string */
+ value = p;
+ value_length = length;
+ }
+
+ ret = string_append(ret, value, value_length);
+
+ /* advance past the specifier */
+ p = end + 1;
+ }
+ break;
+
+ case '#':
+ /* let the escape character escape itself */
+ if (p[1] != '\0') {
+ ret = string_append(ret, p + 1, 1);
+ p += 2;
+ break;
+ }
+
+ /* fall through */
+
+ default:
+ /* pass-through non-escaped portions of the format string */
+ ret = string_append(ret, p, 1);
+ ++p;
+ }
+ }
+
+ if (last != NULL)
+ *last = p;
+ return ret;
+}
+
+char *
+format_object(const char *format, const void *object,
+ const char *(*getter)(const void *object, const char *name))
+{
+ return format_object2(format, NULL, object, getter);
+}