Skip to content

Commit

Permalink
Implement escaping
Browse files Browse the repository at this point in the history
  • Loading branch information
imDaniX committed Apr 14, 2023
1 parent 999f778 commit 5e9d90b
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 215 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ and MiniMessage projects. An attempt to make legacy-friendly serializer with mod
## TODO
- Gradient stuff
- Placeholders (for keys, translations, selectors)
- Escaping
- Actually implement serializer (as all we have now is deserializer lol)
- Closing colors(?)

## Get it
Right now everything is handled using [JitPack](https://jitpack.io/#GlowingInk/InkyMessage/master-SNAPSHOT)
Right now everything is handled using [JitPack](https://jitpack.io/#GlowingInk/InkyMessage)

### Maven
Add to repositories
Expand All @@ -25,7 +24,7 @@ Add to dependencies
<dependency>
<groupId>com.github.GlowingInk</groupId>
<artifactId>InkyMessage</artifactId>
<version>master-SNAPSHOT</version>
<version>0.3</version> <!-- Or master-SNAPSHOT -->
</dependency>
```
### Others
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>ink.glowing</groupId>
<artifactId>inkymessage</artifactId>
<version>0.2.0</version>
<version>0.3</version>

<properties>
<adventure.version>4.13.1</adventure.version>
Expand Down
95 changes: 0 additions & 95 deletions src/main/java/ink/glowing/adventure/InkyMessage.java

This file was deleted.

8 changes: 0 additions & 8 deletions src/main/java/ink/glowing/adventure/text/IncludedText.java

This file was deleted.

135 changes: 135 additions & 0 deletions src/main/java/ink/glowing/text/InkyMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package ink.glowing.text;

import ink.glowing.text.modifier.ClickModifier;
import ink.glowing.text.modifier.ColorModifier;
import ink.glowing.text.modifier.FontModifier;
import ink.glowing.text.modifier.HoverModifier;
import ink.glowing.text.modifier.ModifiersResolver;
import ink.glowing.text.utils.Utils;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentIteratorType;
import net.kyori.adventure.text.KeybindComponent;
import net.kyori.adventure.text.ScoreComponent;
import net.kyori.adventure.text.SelectorComponent;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.serializer.ComponentSerializer;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import static ink.glowing.text.utils.Utils.SECTION;

public enum InkyMessage implements ComponentSerializer<Component, Component, String> {
INSTANCE;

private static final Pattern ESCAPE_PATTERN = Pattern.compile("[&\\]()]");
private static final Pattern UNESCAPE_PATTERN = Pattern.compile("\\\\([&\\]()])");

private static final ModifiersResolver DEFAULT_MODIFIERS = new ModifiersResolver(
ColorModifier.INSTANCE,
HoverModifier.INSTANCE,
ClickModifier.INSTANCE,
FontModifier.INSTANCE
);

public @NotNull Component deserialize(@NotNull String input, @NotNull ModifiersResolver modsResolver) {
List<RichText> richTexts = new ArrayList<>();
String oldText = input;
String newText = parseRich(input, richTexts, modsResolver);
while (!newText.equals(oldText)) {
oldText = newText;
newText = parseRich(oldText, richTexts, modsResolver);
}
return new RichText(newText, List.of()).render(richTexts).component().compact();
}

private static @NotNull String parseRich(@NotNull String input, @NotNull List<RichText> richTexts, @NotNull ModifiersResolver modsResolver) {
int closeIndex = input.indexOf("](");
while (Utils.isEscaped(input, closeIndex)) closeIndex = input.indexOf("](", closeIndex + 1);
if (closeIndex == -1) return input;
int startIndex = -1;
for (int index = closeIndex - 1; index > 0; index--) {
if (input.charAt(index) == '[' && input.charAt(index - 1) == '&' && !Utils.isEscaped(input, index - 1)) {
startIndex = index - 1;
break;
}
}
if (startIndex == -1) return input;
int modStart = closeIndex + 1;
int modEnd = -1;
for (int index = modStart + 1; index < input.length(); index++) {
char ch = input.charAt(index);
if (ch == ')' && !Utils.isEscaped(input, index)) {
if (index + 1 == input.length() || input.charAt(index + 1) != '(') {
modEnd = index;
}
}
}
++modEnd;
richTexts.add(new RichText(
input.substring(startIndex + 2, closeIndex),
modsResolver.parseModifiers(input.substring(modStart, modEnd))
));
return input.substring(0, startIndex) + SECTION + (richTexts.size() - 1) + SECTION + input.substring(modEnd);
}

public static @NotNull String escapeAll(@NotNull String text) {
return ESCAPE_PATTERN.matcher(text).replaceAll(result -> "\\\\" + result.group());
}

public static @NotNull String unescapeAll(@NotNull String text) {
return UNESCAPE_PATTERN.matcher(text).replaceAll(result -> result.group(1));
}

public static @NotNull String escapeSingularSlash(@NotNull String text) {
StringBuilder builder = new StringBuilder(text.length());
boolean escaped = false;
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (escaped) {
escaped = false;
if ("&]()\\".indexOf(ch) == -1) {
builder.append("\\");
}
} else if (ch == '\\') {
escaped = true;
}
builder.append(ch);
}
if (escaped) builder.append('\\');
return builder.toString();
}

@Override
public @NotNull Component deserialize(@NotNull String text) {
return deserialize(text, DEFAULT_MODIFIERS);
}

@Override
public @NotNull String serialize(@NotNull Component component) {
StringBuilder builder = new StringBuilder();
for (var child : component.iterable(ComponentIteratorType.BREADTH_FIRST)) {
serialize(builder, child);
}
return builder.toString();
}

private void serialize(@NotNull StringBuilder builder, @NotNull Component component) {
if (component instanceof TextComponent text) {
builder.append(text.content());
} else if (component instanceof TranslatableComponent translatable) {
builder.append(translatable.key());
} else if (component instanceof KeybindComponent keybind) {
builder.append(keybind.keybind());
} else if (component instanceof ScoreComponent score) {
builder.append(score.objective());
} else if (component instanceof SelectorComponent selector) {
builder.append(selector.pattern());
} else {
builder.append('?');
}
}
}
Original file line number Diff line number Diff line change
@@ -1,72 +1,82 @@
package ink.glowing.adventure.text;
package ink.glowing.text;

import ink.glowing.adventure.modifier.Modifier;
import ink.glowing.adventure.utils.AdventureUtils;
import ink.glowing.text.modifier.Modifier;
import ink.glowing.text.utils.Utils;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import org.jetbrains.annotations.NotNull;

import java.util.List;

import static ink.glowing.adventure.utils.AdventureUtils.SPECIAL_CHAR;
import static ink.glowing.text.utils.Utils.SECTION_CHAR;
import static net.kyori.adventure.text.Component.text;

public class RichText {
public static final RichText EMPTY = new RichText("", List.of()) {
@Override
public @NotNull IncludedText render(List<RichText> richTexts) {
return new IncludedText(Component.empty(), Style.empty());
public @NotNull RichText.Included render(List<RichText> richTexts) {
return new Included(Component.empty(), Style.empty());
}
};

private final String text;
private final boolean hasSlashes;
private final List<Modifier.Prepared> modifiers;

public RichText(@NotNull String text, @NotNull List<Modifier.Prepared> modifiers) {
this.text = text;
this.hasSlashes = text.indexOf('\\') != -1;
this.modifiers = modifiers;
}

public @NotNull IncludedText render(List<RichText> richTexts) {
public @NotNull RichText.Included render(List<RichText> richTexts) {
Style lastStyle = Style.empty();
Component result = Component.empty();
int lastAppend = 0;
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch == SPECIAL_CHAR) {
if (ch == SECTION_CHAR) {
result = result.append(text(text.substring(lastAppend, i)).style(lastStyle));
int start = i + 1;
//noinspection StatementWithEmptyBody
while (text.charAt(++i) != SPECIAL_CHAR);
IncludedText included = richTexts.get(Integer.parseInt(text.substring(start, i))).render(richTexts);
while (text.charAt(++i) != SECTION_CHAR);
Included included = richTexts.get(Integer.parseInt(text.substring(start, i))).render(richTexts);
result = result.append(included.component());
if (!included.lastStyle().isEmpty()) lastStyle = included.lastStyle();
lastAppend = i + 1;
} else if (ch == '&') {
if (i + 1 == text.length()) continue;
if (i + 1 == text.length() || Utils.isEscaped(text, i)) continue;
char styleChar = text.charAt(i + 1);
if (styleChar == '#' && i + 8 < text.length()) {
String colorStr = text.substring(i + 1, i + 8);
if (!AdventureUtils.isHexColor(colorStr)) continue;
result = result.append(text(text.substring(lastAppend, i)).style(lastStyle));
if (!Utils.isHexColor(colorStr)) continue;
result = makeComponent(result, lastAppend, i, lastStyle);
lastStyle = lastStyle.color(TextColor.fromHexString(colorStr));
lastAppend = (i += 7) + 1;
} else {
Style newStyle = AdventureUtils.mergeLegacyStyle(styleChar, lastStyle);
Style newStyle = Utils.mergeLegacyStyle(styleChar, lastStyle);
if (newStyle == null) continue;
result = result.append(text(text.substring(lastAppend, i)).style(lastStyle));
result = makeComponent(result, lastAppend, i, lastStyle);
lastStyle = newStyle;
lastAppend = (++i) + 1;
}
}
}
if (lastAppend < text.length()) {
result = result.append(text(text.substring(lastAppend)).style(lastStyle));
result = makeComponent(result, lastAppend, text.length(), lastStyle);
}
for (var rawMod : modifiers) {
result = rawMod.modify(result, richTexts);
}
return new IncludedText(result, lastStyle);
return new Included(result, lastStyle);
}

private @NotNull Component makeComponent(Component comp, int start, int end, Style style) {
String substring = text.substring(start, end);
if (hasSlashes) substring = InkyMessage.unescapeAll(substring);
return comp.append(text(substring).style(style));
}

private record Included(@NotNull Component component, @NotNull Style lastStyle) {}
}
Loading

0 comments on commit 5e9d90b

Please sign in to comment.