Fix SNBT Parsing and writing of single quoted strings, and strings with quotes or single quotes within.

This commit is contained in:
zontreck 2025-01-22 01:48:17 -07:00
parent bdb087fabc
commit 18e98ca918
9 changed files with 93 additions and 14 deletions

View file

@ -503,18 +503,31 @@ class StringReader {
return result.toString();
}
// Read a string enclosed in double quotes
/// Read a string enclosed in double quotes
String readQuotedString() {
_quotedString = true;
if (next() != '"') {
throw Exception('Expected double quotes at the start of a string');
var nxtChar = next();
if (nxtChar != '"' && nxtChar != "'") {
throw Exception(
'Expected double quotes or single quotes at the start of a string');
}
StringBuffer result = StringBuffer();
bool escaping = false;
String quoteDigit = nxtChar;
while (canRead) {
String char = next();
if (char == '"') {
if (char == '"' && quoteDigit == "\"") {
break;
} else if (char == '\\' && peek() == '\'' && quoteDigit == '\'') {
escaping = true;
continue;
} else if (char == '\'' && quoteDigit == '\'') {
if (!escaping) break;
}
escaping = false;
result.write(char);
}
_quotedString = false;
@ -552,13 +565,15 @@ class StringReader {
}
String readString() {
if (peek() == "\"") {
if (peek() == "\"" || peek() == "'") {
return readQuotedString();
} else
return readUnquotedString();
}
// Read a specific character and throw an exception if it's not found
/// Read a specific character and throw an exception if it's not found
///
/// Parameter is case-insensitive
void expect(String expectedChar) {
if (next().toLowerCase() != expectedChar.toLowerCase()) {
throw Exception('Expected $expectedChar');
@ -573,4 +588,10 @@ class StringReader {
_position = _lastPostion;
_lastPostion = 0;
}
@override
String toString() {
// Returns the entire value starting from position
return _buffer.substring(_position);
}
}

View file

@ -67,6 +67,12 @@ enum TagType {
break;
}
if (val == "'") {
reader.next();
isQuoted = true;
break;
}
if (val == '{') {
ret = TagType.Compound; // Detected a CompoundTag
reader.next(); // Consume '{'
@ -251,7 +257,13 @@ abstract class Tag {
TagType type = TagType.getStringifiedTagType(string);
Tag tag = Tag.makeTagOfType(type);
tag._key = name;
tag.readStringifiedValue(string);
try {
tag.readStringifiedValue(string);
} catch (E, stack) {
print(E);
print(string.getSnapshot());
print(stack);
}
return tag;
}
@ -286,6 +298,14 @@ abstract class Tag {
}
}
bool shouldUseSingleQuotes(String value) {
return value.contains("\"");
}
bool shouldEscapeSingleQuotes(String value) {
return value.contains("'");
}
void writeStringifiedValue(StringBuilder builder, int indent, bool isList);
void readStringifiedValue(StringReader reader);

View file

@ -66,6 +66,11 @@ class IntArrayTag extends Tag {
while (reader.peek() != "]") {
value.add(int.parse(reader.readNumber()));
// The SNBT standard does not require a integer to be suffixed by a 'I'.
// This implementation honors that by making it optional.
// FIX 1/21/25 @Aria: Int Array was lacking the skipping of the I digit when it might possibly be present
if (reader.peek().toLowerCase() == "i") reader.expect("I");
if (reader.peek() == ",") reader.next();
}
reader.expect("]");

View file

@ -46,10 +46,17 @@ class StringTag extends Tag {
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
if (shouldQuote(value))
builder.append("${isList ? "".padLeft(indent, '\t') : ""}\"${value}\"");
else
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}");
final useSingleQuotes = shouldUseSingleQuotes(value);
final quote = useSingleQuotes ? '\'' : '"';
final escapeQuote = useSingleQuotes ? '\\\'' : '\\"';
String escapedValue = value;
if (shouldEscapeSingleQuotes(value) && useSingleQuotes) {
escapedValue = value.replaceAll('\'', escapeQuote);
}
builder.append(
"${isList ? "".padLeft(indent, '\t') : ""}${quote}${escapedValue}${quote}");
}
@override