--- /dev/null
+/* Copyright (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (label_sanitization.c).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <playlists/label_sanitization.h>
+#include <compat/strl.h>
+#include <retro_miscellaneous.h>
+#include <string/stdstring.h>
+#include <string.h>
+
+#define DISC_STRINGS_LENGTH 3
+#define REGION_STRINGS_LENGTH 20
+
+const char *disc_strings[DISC_STRINGS_LENGTH] = {
+ "(CD",
+ "(Disc",
+ "(Disk"
+};
+
+/*
+ * We'll use the standard No-Intro regions for now.
+ */
+const char *region_strings[REGION_STRINGS_LENGTH] = {
+ "(Australia)", /* Don’t use with Europe */
+ "(Brazil)",
+ "(Canada)", /* Don’t use with USA */
+ "(China)",
+ "(France)",
+ "(Germany)",
+ "(Hong Kong)",
+ "(Italy)",
+ "(Japan)",
+ "(Korea)",
+ "(Netherlands)",
+ "(Spain)",
+ "(Sweden)",
+ "(USA)", /* Includes Canada */
+ "(World)",
+ "(Europe)", /* Includes Australia */
+ "(Asia)",
+ "(Japan, USA)",
+ "(Japan, Europe)",
+ "(USA, Europe)"
+};
+
+/**
+ * label_sanitize:
+ *
+ * NOTE: Does not work with nested blocks.
+ **/
+void label_sanitize(char *label, bool (*left)(char*), bool (*right)(char*))
+{
+ bool copy = true;
+ int rindex = 0;
+ int lindex = 0;
+ char new_label[PATH_MAX_LENGTH];
+
+ for (; lindex < PATH_MAX_LENGTH && label[lindex] != '\0'; lindex++)
+ {
+ if (copy)
+ {
+ /* check for the start of the range */
+ if ((*left)(&label[lindex]))
+ copy = false;
+ else
+ {
+ const bool whitespace = label[lindex] == ' ' && (rindex == 0 || new_label[rindex - 1] == ' ');
+
+ /* Simplify consecutive whitespaces */
+ if (!whitespace)
+ new_label[rindex++] = label[lindex];
+ }
+ }
+ else if ((*right)(&label[lindex]))
+ copy = true;
+ }
+
+ /* Trim trailing whitespace */
+ if (rindex > 0 && new_label[rindex - 1] == ' ')
+ new_label[rindex - 1] = '\0';
+ else
+ new_label[rindex] = '\0';
+
+ strlcpy(label, new_label, PATH_MAX_LENGTH);
+}
+
+static bool left_parens(char *left)
+{
+ return left[0] == '(';
+}
+
+static bool right_parens(char *right)
+{
+ return right[0] == ')';
+}
+
+static bool left_brackets(char *left)
+{
+ return left[0] == '[';
+}
+
+static bool right_brackets(char *right)
+{
+ return right[0] == ']';
+}
+
+static bool left_parens_or_brackets(char *left)
+{
+ return left[0] == '(' || left[0] == '[';
+}
+
+static bool right_parens_or_brackets(char *right)
+{
+ return right[0] == ')' || right[0] == ']';
+}
+
+static bool left_exclusion(char *left,
+ const char **strings, const size_t strings_count)
+{
+ unsigned i;
+ char exclusion_string[32];
+ char comparison_string[32];
+
+ strlcpy(exclusion_string, left, sizeof(exclusion_string));
+ string_to_upper(exclusion_string);
+
+ for (i = 0; i < (unsigned)strings_count; i++)
+ {
+ strlcpy(comparison_string, strings[i], sizeof(comparison_string));
+ string_to_upper(comparison_string);
+
+ if (string_starts_with(exclusion_string,
+ comparison_string))
+ return true;
+ }
+
+ return false;
+}
+
+static bool left_parens_or_brackets_excluding_region(char *left)
+{
+ return left_parens_or_brackets(left)
+ && !left_exclusion(left, region_strings, REGION_STRINGS_LENGTH);
+}
+
+static bool left_parens_or_brackets_excluding_disc(char *left)
+{
+ return left_parens_or_brackets(left)
+ && !left_exclusion(left, disc_strings, DISC_STRINGS_LENGTH);
+}
+
+static bool left_parens_or_brackets_excluding_region_or_disc(char *left)
+{
+ return left_parens_or_brackets(left)
+ && !left_exclusion(left, region_strings, REGION_STRINGS_LENGTH)
+ && !left_exclusion(left, disc_strings, DISC_STRINGS_LENGTH);
+}
+
+void label_remove_parens(char *label)
+{
+ label_sanitize(label, left_parens, right_parens);
+}
+
+void label_remove_brackets(char *label)
+{
+ label_sanitize(label, left_brackets, right_brackets);
+}
+
+void label_remove_parens_and_brackets(char *label)
+{
+ label_sanitize(label, left_parens_or_brackets,
+ right_parens_or_brackets);
+}
+
+void label_keep_region(char *label)
+{
+ label_sanitize(label, left_parens_or_brackets_excluding_region,
+ right_parens_or_brackets);
+}
+
+void label_keep_disc(char *label)
+{
+ label_sanitize(label, left_parens_or_brackets_excluding_disc,
+ right_parens_or_brackets);
+}
+
+void label_keep_region_and_disc(char *label)
+{
+ label_sanitize(label, left_parens_or_brackets_excluding_region_or_disc,
+ right_parens_or_brackets);
+}