PlaceholderParser failing to replace placeholders in some edge case strings with lots of curly brackets outside placeholders
Keksuccino opened this issue · 0 comments
Description
Description:
When attempting to execute an /item replace command via a FancyMenu action, a Javscript Error: expected "}" syntax error is encountered. This error specifically arises when a nested json placeholder is used to define an NBT tag's value, and the json_path parameter within this json placeholder itself contains a getvariable placeholder.
Problematic Command Snippet (FancyMenu Action Value):
/item replace entity @p hotbar.3 with tacz:ammo{AmmoId:"tacz:{"placeholder":"json","values":{"source":"bst_data/loadout/gun_ammo_types","json_path":"{"placeholder":"getvariable","values":{"name":"sel_pmy_gun"}}"}}"} 20
Steps to Reproduce (Conceptual based on user interaction):
- Define a FancyMenu variable, e.g.,
sel_pmy_gun, with a value representing a valid JSON path (e.g.,field_name). - Create an action (e.g., a button click action) that executes a
sendmessageor similar action containing the/item replacecommand string constructed as shown above. - Trigger the action.
Expected Behavior:
FancyMenu should correctly evaluate all nested placeholders, resulting in a valid string (e.g., tacz:ammo{AmmoId:"tacz:some_final_ammo_id"}) which is then passed to the Minecraft command system, and the command should execute successfully.
Actual Behavior:
A Javscript Error: expected "}" is reported, indicating a syntax error originating from the placeholder evaluation or command string construction process within FancyMenu, before Minecraft itself processes the command.
Additional Context:
- A simpler
/item replacecommand using ajsonplaceholder (without thegetvariablenested injson_path) works without issues:/item replace entity @s weapon.offhand with tacz:modern_kinetic_gun{HasBulletInBarrel: 1b, GunFireMode: "SEMI", GunId: "tacz:{"placeholder":"json","values":{"source":"bst_data/loadout/scd_gun.json","json_path":"scd_gun"}}", GunCurrentAmmoCount: 10}. - The raw command (with manually evaluated placeholder results) functions correctly when entered directly into Minecraft chat.
- The issue seems specific to the deep nesting combination of
json_pathbeing dynamically set bygetvariablewithin thejsonplaceholder, coupled with being embedded insideNBTstring quotes.
This seems like a very specific parsing anomaly that warrants further investigation into the placeholder evaluation logic for such complex nesting scenarios.
Potential Fix
Looking at this bug report and the Java code, I can identify the issue. The problem is in the findPlaceholderEndIndex method, which doesn’t properly handle brackets that appear inside quoted strings.
The Bug
When parsing nested placeholders where the inner placeholder is within a JSON string value (like "json_path":"{"placeholder":"getvariable"...}"), the parser incorrectly counts the brackets inside the quoted string as part of the bracket depth calculation. This causes it to match the wrong closing bracket, resulting in malformed JSON and the “expected }” error.
The Issue in findPlaceholderEndIndex
The current implementation:
private static int findPlaceholderEndIndex(@NotNull String placeholderStartSubString, int startIndex) {
int currentIndex = startIndex;
int depth = 0;
boolean backslash = false;
for (char c : placeholderStartSubString.toCharArray()) {
if (currentIndex != startIndex) { //skip first char
if ((c == OPEN_CURLY_BRACKETS_CHAR) && !backslash) {
depth++; // Counts ALL brackets, even those inside quotes!
} else if ((c == CLOSE_CURLY_BRACKETS_CHAR) && !backslash) {
if (depth <= 0) {
return currentIndex;
} else {
depth--;
}
}
backslash = (c == BACKSLASH_CHAR);
}
currentIndex++;
}
return -1;
}This method tracks bracket depth but doesn’t account for quotes. When brackets appear inside a quoted string (like in your nested placeholder), they shouldn’t affect the depth count.
The Fix
The method needs to track whether it’s currently inside a quoted string, similar to how normalizePlaceholderString already does:
private static int findPlaceholderEndIndex(@NotNull String placeholderStartSubString, int startIndex) {
int currentIndex = startIndex;
int depth = 0;
boolean backslash = false;
boolean inQuotes = false; // Add quote tracking
for (char c : placeholderStartSubString.toCharArray()) {
if (currentIndex != startIndex) { //skip first char
// Handle quote state changes
if ((c == APOSTROPHE_CHAR) && !backslash) {
inQuotes = !inQuotes;
}
// Only count brackets when NOT inside quotes
else if (!inQuotes) {
if ((c == OPEN_CURLY_BRACKETS_CHAR) && !backslash) {
depth++;
} else if ((c == CLOSE_CURLY_BRACKETS_CHAR) && !backslash) {
if (depth <= 0) {
return currentIndex;
} else {
depth--;
}
}
}
backslash = (c == BACKSLASH_CHAR);
}
currentIndex++;
}
return -1;
}Key Changes:
- Added
boolean inQuotes = falseto track whether we’re inside a quoted string - When encountering an unescaped quote (
"), toggle theinQuotesstate - Only count brackets (
{and}) when!inQuotes- brackets inside quoted strings are just part of the string value and shouldn’t affect depth
This fix ensures that the parser correctly identifies the boundaries of the outer placeholder without being confused by brackets that appear within nested placeholders inside quoted JSON values.
Yes, my conclusion remains the same. The additional context actually confirms the issue I identified. Let me explain why:
Why the Additional Context Confirms the Bug
-
The working example (without nested
getvariable) has NO brackets inside the quotedjson_pathvalue:"json_path":"scd_gun" // Just a simple string, no brackets
-
The failing example has brackets INSIDE the quoted
json_pathvalue:"json_path":"{"placeholder":"getvariable","values":{"name":"sel_pmy_gun"}}" // ^-- These brackets are inside quotes but counted by findPlaceholderEndIndex
The Root Cause
When findPlaceholderEndIndex encounters the nested placeholder structure, it:
- Starts at the opening
{of the outerjsonplaceholder - Increments depth for every
{it finds (including those inside the quotedjson_path) - Decrements depth for every
}it finds - Incorrectly matches a closing
}from inside the quoted string as the end of the outer placeholder
This explains why:
- Simple placeholders work (no brackets in quoted values)
- The raw command works when manually evaluated (no placeholder parsing needed)
- The specific nesting pattern fails (brackets inside quoted values confuse the parser)
The Fix Remains the Same
The findPlaceholderEndIndex method needs to track whether it’s inside quotes:
private static int findPlaceholderEndIndex(@NotNull String placeholderStartSubString, int startIndex) {
int currentIndex = startIndex;
int depth = 0;
boolean backslash = false;
boolean inQuotes = false; // Track quote state
for (char c : placeholderStartSubString.toCharArray()) {
if (currentIndex != startIndex) {
// Handle escape sequences
if (backslash) {
backslash = false;
currentIndex++;
continue;
}
// Check for escape character
if (c == BACKSLASH_CHAR) {
backslash = true;
currentIndex++;
continue;
}
// Toggle quote state
if (c == APOSTROPHE_CHAR) {
inQuotes = !inQuotes;
}
// Only count brackets when NOT inside quotes
else if (!inQuotes) {
if (c == OPEN_CURLY_BRACKETS_CHAR) {
depth++;
} else if (c == CLOSE_CURLY_BRACKETS_CHAR) {
if (depth <= 0) {
return currentIndex;
} else {
depth--;
}
}
}
}
currentIndex++;
}
return -1;
}Note: I’ve also improved the escape handling to be cleaner - the previous version had a subtle bug where backslash was set at the end of the loop, which could miss some edge cases.
This fix ensures that brackets inside quoted strings (like your nested getvariable placeholder) are treated as literal string content rather than structural JSON brackets, allowing the parser to correctly identify where the outer placeholder ends.