增强输入处理和文件保存功能

在 `Scanner.cs` 中添加了对 `System.Diagnostics` 的引用,并优化了 `MoveNext` 方法以简化空白字符处理和错误处理逻辑。

在 `StringNbt.cs` 中引入了 `System.IO` 和 `System.Numerics`,并修改了解析逻辑以增强输入处理和错误检查。新增了 `IsLineEnd` 方法,并简化了 `MoveNext` 的调用。添加了 `SaveToFile` 和 `SaveToStream` 方法,以支持将 `CompoundTag` 保存为 SNBT 格式。
This commit is contained in:
Ling 2025-06-22 21:16:35 +08:00
parent 760c75cadc
commit ca2d7f49e1
2 changed files with 105 additions and 22 deletions

View File

@ -1,5 +1,6 @@
using System;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
@ -44,12 +45,26 @@ internal ref struct Scanner
SyntaxError("Unexpected end of input.");
return false;
}
if (skipWhitespace && char.IsWhiteSpace(Current))
//Debug.Write(Current);
if (skipWhitespace && string.IsNullOrWhiteSpace(Current.ToString()))
goto ReadChar;
return true;
}
public bool MoveNext(bool fail)
{
ReadChar:
Position++;
if (Position >= Source.Length)
{
if (fail)
SyntaxError("Unexpected end of input.");
return false;
}
//Debug.Write(Current);
if (char.IsWhiteSpace(Current) && Current != '\n')
goto ReadChar;
return true;
}
public void AssertChar(char c)
{
if (Current != c)

View File

@ -1,10 +1,11 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Numerics;
using System.Text;
using JetBrains.Annotations;
namespace SharpNBT.SNBT;
@ -83,6 +84,12 @@ public static class StringNbt
while (!scanner.IsEndOfInput)
{
// Closing brace encountered, break loop.
if (scanner.Current == '}')
{
// scanner.MoveNext(true, false);
break;
}
// Read the name of the tag
var childName = ParseString(ref scanner, out _);
@ -94,22 +101,22 @@ public static class StringNbt
scanner.MoveNext(true, true);
var tag = ParseTag(childName, ref scanner);
result.Add(tag);
scanner.MoveNext(true, true);
scanner.MoveNext( true);
// Comma encountered, read another tag.
if (scanner.Current == ',')
if (IsLineEnd(scanner.Current))
{
scanner.MoveNext(true, true);
continue;
}
// Closing brace encountered, break loop.
if (scanner.Current == '}')
{
// scanner.MoveNext(true, false);
break;
}
// Invalid character
scanner.SyntaxError($"Expected ',' or '}}', got '{scanner.Current}'.");
}
@ -309,24 +316,28 @@ public static class StringNbt
var list = new List<Tag>();
while (true)
{
var child = ParseTag(null, ref scanner);
list.Add(child);
scanner.MoveNext(true, true);
// Comma encountered, read another tag.
if (scanner.Current == ',')
{
scanner.MoveNext(true, true);
continue;
}
// Closing brace encountered, break loop.
if (scanner.Current == ']')
{
break;
}
var child = ParseTag(null, ref scanner);
list.Add(child);
scanner.MoveNext(true);
// Comma encountered, read another tag.
if (IsLineEnd(scanner.Current))
{
scanner.MoveNext(true, true);
continue;
}
if (scanner.Current == ']')
{
break;
}
// Invalid character
scanner.SyntaxError($"Expected ',' or ']', got '{scanner.Current}'.");
}
@ -349,7 +360,7 @@ public static class StringNbt
var c = char.ToLowerInvariant(scanner.Current);
if (c == ']')
break;
if (char.IsNumber(c) || c == ',')
if (char.IsNumber(c) ||c == ',' || c == '-')
continue;
if (c is not ('b' or 'l'))
scanner.SyntaxError($"Invalid character '{c}' in integer array.");
@ -366,5 +377,62 @@ public static class StringNbt
}
private const StringSplitOptions SplitOpts = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries;
private static readonly char[] SplitSeparators = new[] { ',', 'b', 'B', 'l', 'L' };
private static readonly char[] SplitSeparators = new[] { ',', 'b', 'B', 'l', 'L','\n' };
private static bool IsLineEnd(char c)
{
return c == ',' || c == '\n' || c == '\r';
}
/// <summary>
/// Saves a <see cref="CompoundTag"/> to a file in SNBT format.
/// </summary>
/// <param name="tag">The <see cref="CompoundTag"/> to save.</param>
/// <param name="filePath">The path to the file where the SNBT data will be saved.</param>
/// <param name="prettyPrint">Whether to format the output in a human-readable format.</param>
/// <exception cref="ArgumentNullException">When <paramref name="tag"/> is <see langword="null"/>.</exception>
/// <exception cref="IOException">When there is an error writing to the file.</exception>
public static void SaveToFile(CompoundTag tag, string filePath, bool prettyPrint = false)
{
if (tag == null)
throw new ArgumentNullException(nameof(tag));
string snbt = prettyPrint ? tag.PrettyPrinted() : tag.Stringify(true);
try
{
File.WriteAllText(filePath, snbt, Encoding.UTF8);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or DirectoryNotFoundException)
{
throw new IOException($"Failed to write SNBT to file '{filePath}': {ex.Message}", ex);
}
}
/// <summary>
/// Saves a <see cref="CompoundTag"/> to a file in SNBT format.
/// </summary>
/// <param name="tag">The <see cref="CompoundTag"/> to save.</param>
/// <param name="stream">The stream where the SNBT data will be written.</param>
/// <param name="prettyPrint">Whether to format the output in a human-readable format.</param>
/// <param name="encoding">The text encoding to use. Defaults to UTF-8 if null.</param>
/// <exception cref="ArgumentNullException">When <paramref name="tag"/> or <paramref name="stream"/> is <see langword="null"/>.</exception>
/// <exception cref="IOException">When there is an error writing to the stream.</exception>
public static void SaveToStream(CompoundTag tag, Stream stream, bool prettyPrint = false, Encoding? encoding = null)
{
if (tag == null)
throw new ArgumentNullException(nameof(tag));
if (stream == null)
throw new ArgumentNullException(nameof(stream));
if (!stream.CanWrite)
throw new IOException("Cannot write to the specified stream.");
string snbt = prettyPrint ? tag.PrettyPrinted() : tag.Stringify(true);
encoding ??= Encoding.UTF8;
using var writer = new StreamWriter(stream, encoding, leaveOpen: true);
writer.Write(snbt);
writer.Flush();
}
}