diff --git a/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/parsing/Parser.java b/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/parsing/Parser.java index f7fe7aee..12459d40 100644 --- a/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/parsing/Parser.java +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/parsing/Parser.java @@ -123,6 +123,22 @@ class Parser { } } + public static String addEscapes(String string) { + StringBuilder output = new StringBuilder(string.length() + 16); // String gets longer with escapes + + for (int i = 0; i < string.length(); i++) { + char currentChar = string.charAt(i); + + if (isSpecialCharacter(currentChar)) { + output.append(ESCAPE_CHAR); + } + + output.append(currentChar); + } + + return output.toString(); + } + private static boolean isSpecialCharacter(char currentChar) { return currentChar == ESCAPE_CHAR || currentChar == PLACEHOLDER_START_CHAR diff --git a/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/parsing/StringWithPlaceholders.java b/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/parsing/StringWithPlaceholders.java index b116216f..ba094cc2 100644 --- a/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/parsing/StringWithPlaceholders.java +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/parsing/StringWithPlaceholders.java @@ -27,6 +27,10 @@ public final class StringWithPlaceholders { return Parser.parse(string, true); } + public static String addEscapes(@NotNull String string) { + return Parser.addEscapes(string); + } + StringWithPlaceholders(@NotNull String string, @Nullable List parts) { this.string = string; this.parts = parts; @@ -71,7 +75,7 @@ public final class StringWithPlaceholders { return replace(player, replaceFunction, StringReplaceFunction.NO_REPLACEMENTS); } - public @NotNull String replaceStrings(StringReplaceFunction replaceFunction) { + public @NotNull String replaceOutsidePlaceholders(StringReplaceFunction replaceFunction) { return replace(null, PlaceholderReplaceFunction.NO_REPLACEMENTS, replaceFunction); } diff --git a/core/src/test/java/me/filoghost/holographicdisplays/core/placeholder/parsing/StringWithPlaceholdersTest.java b/core/src/test/java/me/filoghost/holographicdisplays/core/placeholder/parsing/StringWithPlaceholdersTest.java index f57dbba3..b02d29bf 100644 --- a/core/src/test/java/me/filoghost/holographicdisplays/core/placeholder/parsing/StringWithPlaceholdersTest.java +++ b/core/src/test/java/me/filoghost/holographicdisplays/core/placeholder/parsing/StringWithPlaceholdersTest.java @@ -37,6 +37,8 @@ class StringWithPlaceholdersTest { Arguments.of(" {p} ", " # "), Arguments.of("{p} {p} {p}", "# # #"), Arguments.of("{{p}}", "{#}"), // Only the innermost placeholder should be replaced + Arguments.of("{{p}", "{#"), + Arguments.of("{p}}", "#}"), Arguments.of("{p abc", "{p abc"), // Placeholder without closing tag Arguments.of("abc p}", "abc p}") // Placeholder without opening tag ); @@ -46,7 +48,7 @@ class StringWithPlaceholdersTest { @MethodSource("replaceLiteralPartsTestArguments") void replaceLiteralParts(String input, String expectedOutput) { StringWithPlaceholders s = StringWithPlaceholders.of(input); - assertThat(s.replaceStrings(literalPart -> "_")).isEqualTo(expectedOutput); + assertThat(s.replaceOutsidePlaceholders(literalPart -> "_")).isEqualTo(expectedOutput); } static Stream replaceLiteralPartsTestArguments() { @@ -112,7 +114,7 @@ class StringWithPlaceholdersTest { @ParameterizedTest(name = "[{index}] {0} -> {0}") @MethodSource("escapesTestArguments") void preservingEscapes(String input) { - String output = StringWithPlaceholders.withEscapes(input).replaceStrings(StringReplaceFunction.NO_REPLACEMENTS); + String output = StringWithPlaceholders.withEscapes(input).replaceOutsidePlaceholders(StringReplaceFunction.NO_REPLACEMENTS); assertThat(output).isEqualTo(input); } @@ -133,4 +135,10 @@ class StringWithPlaceholdersTest { ); } + @Test + void addEscapes() { + String escaped = StringWithPlaceholders.addEscapes("} { \\"); + assertThat(escaped).isEqualTo("\\} \\{ \\\\"); + } + } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/InternalHologramLineParser.java b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/InternalHologramLineParser.java index 66f5f239..a5a85536 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/InternalHologramLineParser.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/InternalHologramLineParser.java @@ -8,22 +8,25 @@ package me.filoghost.holographicdisplays.plugin.config; import me.filoghost.fcommons.Colors; import me.filoghost.fcommons.MaterialsHelper; import me.filoghost.fcommons.Strings; +import me.filoghost.holographicdisplays.core.placeholder.parsing.StringWithPlaceholders; import me.filoghost.holographicdisplays.plugin.format.DisplayFormat; import me.filoghost.holographicdisplays.plugin.internal.hologram.InternalHologramLine; import me.filoghost.holographicdisplays.plugin.internal.hologram.ItemInternalHologramLine; import me.filoghost.holographicdisplays.plugin.internal.hologram.TextInternalHologramLine; import me.filoghost.holographicdisplays.plugin.lib.nbt.parser.MojangsonParseException; import me.filoghost.holographicdisplays.plugin.lib.nbt.parser.MojangsonParser; -import me.filoghost.holographicdisplays.core.placeholder.parsing.StringWithPlaceholders; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class InternalHologramLineParser { private static final String ICON_PREFIX = "icon:"; + private static final Pattern PLACEHOLDER_API_SHORT_FORMAT = Pattern.compile("%(.+?)%"); public static InternalHologramLine parseLine(String serializedLine) throws InternalHologramLoadException { if (serializedLine.toLowerCase(Locale.ROOT).startsWith(ICON_PREFIX)) { @@ -32,13 +35,42 @@ public class InternalHologramLineParser { return new ItemInternalHologramLine(serializedLine, icon); } else { - String displayText = DisplayFormat.apply(serializedLine, false); - // Apply colors only outside placeholders - displayText = StringWithPlaceholders.withEscapes(displayText).replaceStrings(Colors::colorize); + String displayText = parseText(serializedLine); return new TextInternalHologramLine(serializedLine, displayText); } } + protected static String parseText(String serializedLine) { + String displayText = DisplayFormat.apply(serializedLine, false); + if (Settings.placeholderAPIExpandShortFormat) { + displayText = expandPlaceholderAPIShortFormat(displayText); + } + // Apply colors only outside placeholders + displayText = StringWithPlaceholders.withEscapes(displayText).replaceOutsidePlaceholders(Colors::colorize); + return displayText; + } + + private static String expandPlaceholderAPIShortFormat(String text) { + Matcher matcher = PLACEHOLDER_API_SHORT_FORMAT.matcher(text); + boolean foundMatch = matcher.find(); + + if (!foundMatch) { + return text; + } + + StringBuffer result = new StringBuffer(); + + while (foundMatch) { + String placeholderContent = matcher.group(1); + matcher.appendReplacement(result, ""); + result.append("{papi: ").append(StringWithPlaceholders.addEscapes(placeholderContent)).append("}"); + foundMatch = matcher.find(); + } + + matcher.appendTail(result); + return result.toString(); + } + @SuppressWarnings("deprecation") private static ItemStack parseItemStack(String serializedItem) throws InternalHologramLoadException { serializedItem = serializedItem.trim(); diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/Settings.java b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/Settings.java index d357ae44..530202ac 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/Settings.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/Settings.java @@ -26,6 +26,7 @@ public class Settings { public static boolean updateNotification; public static boolean placeholderAPIEnabled; + public static boolean placeholderAPIExpandShortFormat; public static int placeholderAPIDefaultRefreshInternalTicks; public static String imageSymbol; @@ -49,6 +50,7 @@ public class Settings { updateNotification = config.updateNotification; placeholderAPIEnabled = config.placeholderAPIEnabled; + placeholderAPIExpandShortFormat = config.placeholderAPIShortFormat; placeholderAPIDefaultRefreshInternalTicks = config.placeholderAPIDefaultRefreshIntervalTicks; imageSymbol = DisplayFormat.apply(config.imageRenderingSolidPixel); diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/SettingsModel.java b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/SettingsModel.java index 20c3487c..36315d15 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/SettingsModel.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/config/SettingsModel.java @@ -27,6 +27,9 @@ public class SettingsModel implements MappedConfig { @Path("placeholders.PlaceholderAPI.enabled") boolean placeholderAPIEnabled = true; + @Path("placeholders.PlaceholderAPI.expand-short-format") + boolean placeholderAPIShortFormat = true; + @Path("placeholders.PlaceholderAPI.default-refresh-interval-ticks") int placeholderAPIDefaultRefreshIntervalTicks = 200; diff --git a/plugin/src/test/java/me/filoghost/holographicdisplays/plugin/config/InternalHologramLineParserTest.java b/plugin/src/test/java/me/filoghost/holographicdisplays/plugin/config/InternalHologramLineParserTest.java new file mode 100644 index 00000000..8dcdd2b8 --- /dev/null +++ b/plugin/src/test/java/me/filoghost/holographicdisplays/plugin/config/InternalHologramLineParserTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.plugin.config; + +import org.bukkit.ChatColor; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.*; + +class InternalHologramLineParserTest { + + @ParameterizedTest(name = "[{index}] {0} -> {1}") + @MethodSource({"parseTextArguments"}) + void parseText(String input, String expectedOutput) { + Settings.placeholderAPIExpandShortFormat = true; + String output = InternalHologramLineParser.parseText(input); + assertThat(output).isEqualTo(expectedOutput); + } + + static Stream parseTextArguments() { + return Stream.of( + // PlaceholderAPI replacing and escaping + Arguments.of("test1 %test2% test3", "test1 {papi: test2} test3"), + Arguments.of("%&0colored%", "{papi: &0colored}"), + Arguments.of("%test1% {test2} %test3%", "{papi: test1} {test2} {papi: test3}"), + Arguments.of("%test_{escape\\this}%", "{papi: test_\\{escape\\\\this\\}}"), + Arguments.of("%t{e%s}t", "{papi: t\\{e}s}t"), + Arguments.of("{inside %placeholder%}", "{inside {papi: placeholder}}"), + Arguments.of("\\{keep escapes\\}", "\\{keep escapes\\}"), + Arguments.of("single % symbol", "single % symbol"), + + // Placeholders and colors + Arguments.of("&0 {p: &0}", ChatColor.COLOR_CHAR + "0 {p: &0}") + ); + } + +}