Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ToonFormat.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Solution>
<Configurations>
<Platform Name="Any CPU" />
<Platform Name="x64" />
<Platform Name="x86" />
</Configurations>
<Folder Name="/src/">
<Project Path="src/ToonFormat/ToonFormat.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/ToonFormat.Tests/ToonFormat.Tests.csproj" />
</Folder>
</Solution>
2 changes: 1 addition & 1 deletion src/ToonFormat/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public static bool IsStructural(char c)
/// TOON's unified options configuration, styled to align with System.Text.Json. Used to control indentation,
/// delimiters, strict mode, length markers, and underlying JSON behavior.
/// </summary>
public enum ToonDelimiter
public enum ToonDelimiter : byte
{
/// <summary>Comma ,</summary>
COMMA,
Expand Down
3 changes: 0 additions & 3 deletions src/ToonFormat/Internal/Decode/Scanner.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;

namespace ToonFormat.Internal.Decode
{
Expand All @@ -10,7 +9,6 @@ namespace ToonFormat.Internal.Decode
/// </summary>
internal class ParsedLine
{
public string Raw { get; set; } = string.Empty;
public int Indent { get; set; }
public string Content { get; set; } = string.Empty;
public int Depth { get; set; }
Expand Down Expand Up @@ -188,7 +186,6 @@ public static ScanResult ToParsedLines(string source, int indentSize, bool stric
}
parsed.Add(new ParsedLine
{
Raw = new string(lineSpan),
Indent = indent,
Content = new string(contentSpan),
Depth = lineDepth,
Expand Down
38 changes: 23 additions & 15 deletions src/ToonFormat/Internal/Encode/LineWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ namespace ToonFormat.Internal.Encode
/// </summary>
internal class LineWriter
{
private readonly List<string> _lines = new();
private readonly string _indentationString;
private readonly StringBuilder _builder = new();
private readonly string _indentationUnit;
private readonly List<string> _indentCache = new() { string.Empty };
private bool _hasAnyLine;

/// <summary>
/// Creates a new LineWriter with the specified indentation size.
/// </summary>
/// <param name="indentSize">Number of spaces per indentation level.</param>
public LineWriter(int indentSize)
{
_indentationString = new string(' ', indentSize);
_indentationUnit = new string(' ', indentSize);
}

/// <summary>
Expand All @@ -29,8 +31,17 @@ public LineWriter(int indentSize)
/// <param name="content">The content of the line.</param>
public void Push(int depth, string content)
{
var indent = RepeatString(_indentationString, depth);
_lines.Add(indent + content);
if (_hasAnyLine)
{
_builder.Append('\n');
}
else
{
_hasAnyLine = true;
}

_builder.Append(GetIndent(depth));
_builder.Append(content);
}

/// <summary>
Expand All @@ -48,23 +59,20 @@ public void PushListItem(int depth, string content)
/// </summary>
public override string ToString()
{
return string.Join("\n", _lines);
return _builder.ToString();
}

/// <summary>
/// Helper method to repeat a string n times.
/// </summary>
private static string RepeatString(string str, int count)
private string GetIndent(int depth)
{
if (count <= 0)
if (depth <= 0)
return string.Empty;

var sb = new StringBuilder(str.Length * count);
for (int i = 0; i < count; i++)
while (_indentCache.Count <= depth)
{
sb.Append(str);
_indentCache.Add(string.Concat(_indentCache[^1], _indentationUnit));
}
return sb.ToString();

return _indentCache[depth];
}
}
}
6 changes: 4 additions & 2 deletions src/ToonFormat/Internal/Encode/Normalize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ internal static class Normalize
var type = value.GetType();
var properties = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);

foreach (var prop in properties.Where(prop => prop.CanRead))
foreach (var prop in properties)
{
if (!prop.CanRead)
continue;
var propValue = prop.GetValue(value);
jsonObject[prop.Name] = NormalizeValue(propValue);
}
Expand Down Expand Up @@ -211,7 +213,7 @@ internal static class Normalize
/// <summary>
/// Determines if a value is a plain object (not a primitive, collection, or special type).
/// </summary>
private static bool IsPlainObject(object value)
private static bool IsPlainObject(object? value)
{
if (value == null)
return false;
Expand Down
49 changes: 31 additions & 18 deletions src/ToonFormat/Internal/Encode/Primitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,17 @@ public static string EncodeKey(string key)
/// </summary>
public static string EncodeAndJoinPrimitives(IEnumerable<JsonNode?> values, char delimiter = Constants.COMMA)
{
var encoded = values.Select(v => EncodePrimitive(v, delimiter));
return string.Join(delimiter.ToString(), encoded);
var sb = new StringBuilder();
bool first = true;
foreach (var value in values)
{
if (!first)
sb.Append(delimiter);
first = false;

sb.Append(EncodePrimitive(value, delimiter));
}
return sb.ToString();
}

// #endregion
Expand All @@ -116,32 +125,36 @@ public static string FormatHeader(
char? delimiter = null)
{
var delimiterChar = delimiter ?? Constants.DEFAULT_DELIMITER_CHAR;
var header = string.Empty;
var sb = new StringBuilder();

// Add key if present
if (!string.IsNullOrEmpty(key))
{
header += EncodeKey(key);
}
sb.Append(EncodeKey(key));

// Add array length with optional marker and delimiter
var delimiterSuffix = delimiterChar != Constants.DEFAULT_DELIMITER_CHAR
? delimiterChar.ToString()
: string.Empty;

header += $"{Constants.OPEN_BRACKET}{length}{delimiterSuffix}{Constants.CLOSE_BRACKET}";
sb.Append(Constants.OPEN_BRACKET);
if (lengthMarker)
sb.Append(Constants.HASH);
sb.Append(length);
if (delimiterChar != Constants.DEFAULT_DELIMITER_CHAR)
sb.Append(delimiterChar);
sb.Append(Constants.CLOSE_BRACKET);

// Add field names for tabular format
if (fields != null && fields.Count > 0)
if (fields is { Count: > 0 })
{
var quotedFields = fields.Select(EncodeKey);
var fieldsStr = string.Join(delimiterChar.ToString(), quotedFields);
header += $"{Constants.OPEN_BRACE}{fieldsStr}{Constants.CLOSE_BRACE}";
sb.Append(Constants.OPEN_BRACE);
for (int i = 0; i < fields.Count; i++)
{
if (i > 0)
sb.Append(delimiterChar);
sb.Append(EncodeKey(fields[i]));
}
sb.Append(Constants.CLOSE_BRACE);
}

header += Constants.COLON;

return header;
sb.Append(Constants.COLON);
return sb.ToString();
}

// #endregion
Expand Down
48 changes: 48 additions & 0 deletions src/ToonFormat/ToonDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,54 @@ public static class ToonDecoder
return JsonSerializer.Deserialize<T>(json);
}

/// <summary>
/// Decodes TOON data from a UTF-8 byte span into a JsonNode with default options.
/// </summary>
/// <param name="utf8Bytes">UTF-8 encoded TOON text.</param>
public static JsonNode? Decode(ReadOnlySpan<byte> utf8Bytes)
{
return Decode(utf8Bytes, new ToonDecodeOptions());
}

/// <summary>
/// Decodes TOON data from a UTF-8 byte span into a JsonNode with custom options.
/// </summary>
/// <param name="utf8Bytes">UTF-8 encoded TOON text.</param>
/// <param name="options">Decoding options to customize parsing behavior.</param>
public static JsonNode? Decode(ReadOnlySpan<byte> utf8Bytes, ToonDecodeOptions? options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));

var text = Encoding.UTF8.GetString(utf8Bytes);
return Decode(text, options);
}

/// <summary>
/// Decodes TOON data from a UTF-8 byte span into the specified type with default options.
/// </summary>
/// <typeparam name="T">Target type to deserialize into.</typeparam>
/// <param name="utf8Bytes">UTF-8 encoded TOON text.</param>
public static T? Decode<T>(ReadOnlySpan<byte> utf8Bytes)
{
return Decode<T>(utf8Bytes, new ToonDecodeOptions());
}

/// <summary>
/// Decodes TOON data from a UTF-8 byte span into the specified type with custom options.
/// </summary>
/// <typeparam name="T">Target type to deserialize into.</typeparam>
/// <param name="utf8Bytes">UTF-8 encoded TOON text.</param>
/// <param name="options">Decoding options to customize parsing behavior.</param>
public static T? Decode<T>(ReadOnlySpan<byte> utf8Bytes, ToonDecodeOptions? options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));

var text = Encoding.UTF8.GetString(utf8Bytes);
return Decode<T>(text, options);
}

/// <summary>
/// Decodes TOON data from a UTF-8 byte array into a JsonNode with default options.
/// </summary>
Expand Down
Loading
Loading