Skip to content

Commit 80d6dff

Browse files
committed
Add SkipHeader option
1 parent a8a55e5 commit 80d6dff

File tree

6 files changed

+137
-10
lines changed

6 files changed

+137
-10
lines changed

ValveKeyValue/ValveKeyValue.Test/Test Data/apisurface.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,15 @@ public sealed class ValveKeyValue.KVSerializerOptions
323323
public bool get_EnableValveNullByteBugBehavior();
324324
public ValveKeyValue.IIncludedFileLoader get_FileLoader();
325325
public bool get_HasEscapeSequences();
326+
public bool get_SkipHeader();
326327
public ValveKeyValue.StringTable get_StringTable();
327328
public int GetHashCode();
328329
public Type GetType();
329330
protected object MemberwiseClone();
330331
public void set_EnableValveNullByteBugBehavior(bool value);
331332
public void set_FileLoader(ValveKeyValue.IIncludedFileLoader value);
332333
public void set_HasEscapeSequences(bool value);
334+
public void set_SkipHeader(bool value);
333335
public void set_StringTable(ValveKeyValue.StringTable value);
334336
public string ToString();
335337
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System.Text;
2+
3+
namespace ValveKeyValue.Test.TextKV3
4+
{
5+
class SkipHeaderTestCase
6+
{
7+
[Test]
8+
public void DeserializesWithoutHeader()
9+
{
10+
var options = new KVSerializerOptions { SkipHeader = true };
11+
var kv = KVSerializer.Create(KVSerializationFormat.KeyValues3Text);
12+
var data = kv.Deserialize("{\n\tkey = \"value\"\n}\n", options);
13+
14+
Assert.That((string)data["key"], Is.EqualTo("value"));
15+
}
16+
17+
[Test]
18+
public void DeserializesWithoutHeaderReturnsEmptyHeader()
19+
{
20+
var options = new KVSerializerOptions { SkipHeader = true };
21+
var kv = KVSerializer.Create(KVSerializationFormat.KeyValues3Text);
22+
var data = kv.Deserialize("{\n\tkey = \"value\"\n}\n", options);
23+
24+
Assert.Multiple(() =>
25+
{
26+
Assert.That(data.Header, Is.Not.Null);
27+
Assert.That(data.Header.Encoding.Name, Is.Null);
28+
Assert.That(data.Header.Format.Name, Is.Null);
29+
});
30+
}
31+
32+
[Test]
33+
public void SerializesWithoutHeader()
34+
{
35+
var options = new KVSerializerOptions { SkipHeader = true };
36+
var kv = KVSerializer.Create(KVSerializationFormat.KeyValues3Text);
37+
var root = KVObject.Collection();
38+
root.Add("key", "value");
39+
var doc = new KVDocument(null!, null!, root);
40+
41+
string text;
42+
using (var ms = new MemoryStream())
43+
{
44+
kv.Serialize(ms, doc, options);
45+
ms.Seek(0, SeekOrigin.Begin);
46+
using var reader = new StreamReader(ms);
47+
text = reader.ReadToEnd();
48+
}
49+
50+
Assert.That(text, Does.Not.StartWith("<!--"));
51+
Assert.That(text, Is.EqualTo("{\n\tkey = \"value\"\n}\n"));
52+
}
53+
54+
[Test]
55+
public void RoundTripsWithoutHeader()
56+
{
57+
var options = new KVSerializerOptions { SkipHeader = true };
58+
var kv = KVSerializer.Create(KVSerializationFormat.KeyValues3Text);
59+
var root = KVObject.Collection();
60+
root.Add("key", "value");
61+
root.Add("number", 42);
62+
var doc = new KVDocument(null!, null!, root);
63+
64+
using var ms = new MemoryStream();
65+
kv.Serialize(ms, doc, options);
66+
ms.Seek(0, SeekOrigin.Begin);
67+
var data = kv.Deserialize(ms, options);
68+
69+
Assert.Multiple(() =>
70+
{
71+
Assert.That((string)data["key"], Is.EqualTo("value"));
72+
Assert.That((int)data["number"], Is.EqualTo(42));
73+
});
74+
}
75+
76+
[Test]
77+
public void DeserializeWithoutHeaderFailsWhenHeaderPresent()
78+
{
79+
var options = new KVSerializerOptions { SkipHeader = true };
80+
var kv = KVSerializer.Create(KVSerializationFormat.KeyValues3Text);
81+
var input = "<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->\n{\n\tkey = \"value\"\n}\n";
82+
83+
Assert.That(() => kv.Deserialize(input, options), Throws.Exception);
84+
}
85+
86+
[Test]
87+
public void DeserializeWithHeaderFailsWhenHeaderMissing()
88+
{
89+
var kv = KVSerializer.Create(KVSerializationFormat.KeyValues3Text);
90+
var input = "{\n\tkey = \"value\"\n}\n";
91+
92+
Assert.That(() => kv.Deserialize(input), Throws.Exception.TypeOf<InvalidDataException>());
93+
}
94+
95+
[Test]
96+
public void SerializesWithHeaderByDefault()
97+
{
98+
var kv = KVSerializer.Create(KVSerializationFormat.KeyValues3Text);
99+
var root = KVObject.Collection();
100+
root.Add("key", "value");
101+
var doc = new KVDocument(null!, null!, root);
102+
103+
string text;
104+
using (var ms = new MemoryStream())
105+
{
106+
kv.Serialize(ms, doc);
107+
ms.Seek(0, SeekOrigin.Begin);
108+
using var reader = new StreamReader(ms);
109+
text = reader.ReadToEnd();
110+
}
111+
112+
Assert.That(text, Does.StartWith("<!-- kv3 encoding:"));
113+
}
114+
}
115+
}

ValveKeyValue/ValveKeyValue/Deserialization/KeyValues3/KV3TextReader.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ namespace ValveKeyValue.Deserialization.KeyValues3
55
{
66
sealed class KV3TextReader : IVisitingReader
77
{
8-
public KV3TextReader(TextReader textReader, IParsingVisitationListener listener)
8+
public KV3TextReader(TextReader textReader, IParsingVisitationListener listener, bool skipHeader = false)
99
{
1010
ArgumentNullException.ThrowIfNull(textReader);
1111
ArgumentNullException.ThrowIfNull(listener);
1212

1313
this.listener = listener;
14+
this.skipHeader = skipHeader;
1415

1516
tokenReader = new KV3TokenReader(textReader);
1617
stateMachine = new KV3TextReaderStateMachine();
@@ -22,13 +23,14 @@ public KV3TextReader(TextReader textReader, IParsingVisitationListener listener)
2223

2324
readonly KV3TokenReader tokenReader;
2425
readonly KV3TextReaderStateMachine stateMachine;
26+
readonly bool skipHeader;
2527
bool disposed;
2628

2729
public KVHeader ReadHeader()
2830
{
2931
ObjectDisposedException.ThrowIf(disposed, this);
3032

31-
var header = tokenReader.ReadHeader();
33+
var header = skipHeader ? new KVHeader() : tokenReader.ReadHeader();
3234

3335
while (stateMachine.IsInObject)
3436
{

ValveKeyValue/ValveKeyValue/KVSerializer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ IVisitingReader MakeReader(Stream stream, IParsingVisitationListener listener, K
126126
{
127127
KVSerializationFormat.KeyValues1Text => new KV1TextReader(new StreamReader(stream, null, true, -1, leaveOpen: true), listener, options),
128128
KVSerializationFormat.KeyValues1Binary => new KV1BinaryReader(stream, listener, options.StringTable!),
129-
KVSerializationFormat.KeyValues3Text => new KV3TextReader(new StreamReader(stream, null, true, -1, leaveOpen: true), listener),
129+
KVSerializationFormat.KeyValues3Text => new KV3TextReader(new StreamReader(stream, null, true, -1, leaveOpen: true), listener, options.SkipHeader),
130130
_ => throw new InvalidOperationException($"Invalid serialization format: {format}"),
131131
};
132132
}
@@ -140,7 +140,7 @@ IVisitationListener MakeSerializer(Stream stream, KVSerializerOptions options, K
140140
{
141141
KVSerializationFormat.KeyValues1Text => new KV1TextSerializer(stream, options),
142142
KVSerializationFormat.KeyValues1Binary => new KV1BinarySerializer(stream, options.StringTable!),
143-
KVSerializationFormat.KeyValues3Text => new KV3TextSerializer(stream, header),
143+
KVSerializationFormat.KeyValues3Text => new KV3TextSerializer(stream, header, options.SkipHeader),
144144
_ => throw new InvalidOperationException($"Invalid serialization format: {format}"),
145145
};
146146
}

ValveKeyValue/ValveKeyValue/KVSerializerOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public sealed class KVSerializerOptions
2828
public IIncludedFileLoader? FileLoader { get; set; }
2929

3030

31+
/// <summary>
32+
/// Gets or sets a value indicating whether the KV3 header comment should be skipped during serialization and deserialization.
33+
/// </summary>
34+
public bool SkipHeader { get; set; }
35+
3136
/// <summary>
3237
/// Gets or sets the string table used for smaller binary serialization.
3338
/// </summary>

ValveKeyValue/ValveKeyValue/Serialization/KeyValues3/KV3TextSerializer.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ sealed class KV3TextSerializer : IVisitationListener, IDisposable
99
{
1010
static readonly SearchValues<char> CharsToEscape = SearchValues.Create("\n\t\\\"");
1111

12-
public KV3TextSerializer(Stream stream, KVHeader? header = null)
12+
public KV3TextSerializer(Stream stream, KVHeader? header = null, bool skipHeader = false)
1313
{
1414
ArgumentNullException.ThrowIfNull(stream);
1515

@@ -18,13 +18,16 @@ public KV3TextSerializer(Stream stream, KVHeader? header = null)
1818
NewLine = "\n"
1919
};
2020

21-
var defaultEncoding = new ValveKeyValue.KeyValues3.KV3ID("text", ValveKeyValue.KeyValues3.Encoding.Text);
22-
var defaultFormat = new ValveKeyValue.KeyValues3.KV3ID("generic", ValveKeyValue.KeyValues3.Format.Generic);
21+
if (!skipHeader)
22+
{
23+
var defaultEncoding = new ValveKeyValue.KeyValues3.KV3ID("text", ValveKeyValue.KeyValues3.Encoding.Text);
24+
var defaultFormat = new ValveKeyValue.KeyValues3.KV3ID("generic", ValveKeyValue.KeyValues3.Format.Generic);
2325

24-
var encoding = header?.Encoding.Name != null ? header.Encoding : defaultEncoding;
25-
var format = header?.Format.Name != null ? header.Format : defaultFormat;
26+
var encoding = header?.Encoding.Name != null ? header.Encoding : defaultEncoding;
27+
var format = header?.Format.Name != null ? header.Format : defaultFormat;
2628

27-
writer.WriteLine($"<!-- kv3 encoding:{encoding} format:{format} -->");
29+
writer.WriteLine($"<!-- kv3 encoding:{encoding} format:{format} -->");
30+
}
2831
}
2932

3033
readonly StreamWriter writer;

0 commit comments

Comments
 (0)