Skip to content

Commit c96cab0

Browse files
committed
chore: Fix internal exception on TOC loading
1 parent 7d095d6 commit c96cab0

File tree

4 files changed

+152
-153
lines changed

4 files changed

+152
-153
lines changed

src/Docfx.Build/TableOfContents/TocHelper.cs

+28-20
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,17 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Immutable;
5-
65
using Docfx.Common;
76
using Docfx.DataContracts.Common;
87
using Docfx.Plugins;
8+
using YamlDotNet.Core;
9+
using YamlDotNet.Core.Events;
10+
using Constants = Docfx.DataContracts.Common.Constants;
911

1012
namespace Docfx.Build.TableOfContents;
1113

1214
public static class TocHelper
1315
{
14-
private static readonly YamlDeserializerWithFallback _deserializer =
15-
YamlDeserializerWithFallback.Create<List<TocItemViewModel>>()
16-
.WithFallback<TocItemViewModel>();
17-
1816
internal static List<FileModel> ResolveToc(ImmutableList<FileModel> models)
1917
{
2018
var tocCache = new Dictionary<string, TocItemInfo>(FilePathComparer.OSPlatformSensitiveStringComparer);
@@ -56,21 +54,20 @@ public static TocItemViewModel LoadSingleToc(string file)
5654
var fileType = Utility.GetTocFileType(file);
5755
try
5856
{
59-
if (fileType == TocFileType.Markdown)
60-
{
61-
return new()
62-
{
63-
Items = MarkdownTocReader.LoadToc(EnvironmentContext.FileAbstractLayer.ReadAllText(file), file)
64-
};
65-
}
66-
else if (fileType == TocFileType.Yaml)
57+
switch (fileType)
6758
{
68-
return _deserializer.Deserialize(file) switch
69-
{
70-
List<TocItemViewModel> vm => new() { Items = vm },
71-
TocItemViewModel root => root,
72-
_ => throw new NotSupportedException($"{file} is not a valid TOC file."),
73-
};
59+
case TocFileType.Markdown:
60+
return new()
61+
{
62+
Items = MarkdownTocReader.LoadToc(EnvironmentContext.FileAbstractLayer.ReadAllText(file), file)
63+
};
64+
case TocFileType.Yaml:
65+
{
66+
var yaml = EnvironmentContext.FileAbstractLayer.ReadAllText(file);
67+
return DeserializeYamlToc(yaml);
68+
}
69+
default:
70+
throw new NotSupportedException($"{file} is not a valid TOC file, supported TOC files should be either \"{Constants.TableOfContents.MarkdownTocFileName}\" or \"{Constants.TableOfContents.YamlTocFileName}\".");
7471
}
7572
}
7673
catch (Exception e)
@@ -79,7 +76,18 @@ public static TocItemViewModel LoadSingleToc(string file)
7976
Logger.LogError(message, code: ErrorCodes.Toc.InvalidTocFile);
8077
throw new DocumentException(message, e);
8178
}
79+
}
80+
81+
private static TocItemViewModel DeserializeYamlToc(string yaml)
82+
{
83+
// Parse yaml content to determine TOC type (`List<TocItemViewModel>` or TocItemViewModel).
84+
var parser = new Parser(new Scanner(new StringReader(yaml), skipComments: true));
85+
bool isListItems = parser.TryConsume<StreamStart>(out var _)
86+
&& parser.TryConsume<DocumentStart>(out var _)
87+
&& parser.TryConsume<SequenceStart>(out var _);
8288

83-
throw new NotSupportedException($"{file} is not a valid TOC file, supported TOC files should be either \"{Constants.TableOfContents.MarkdownTocFileName}\" or \"{Constants.TableOfContents.YamlTocFileName}\".");
89+
return isListItems
90+
? new TocItemViewModel { Items = YamlUtility.Deserialize<List<TocItemViewModel>>(new StringReader(yaml)) }
91+
: YamlUtility.Deserialize<TocItemViewModel>(new StringReader(yaml));
8492
}
8593
}

src/Docfx.Common/YamlDeserializerWithFallback.cs

-62
This file was deleted.
+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text;
5+
using Docfx.Common;
6+
using Docfx.DataContracts.Common;
7+
using FluentAssertions;
8+
using Xunit;
9+
10+
namespace Docfx.Build.TableOfContents.Tests;
11+
12+
public class TocHelperTest
13+
{
14+
[Fact]
15+
public void TestItemDeserialization()
16+
{
17+
// Arrange
18+
var item = new TocItemViewModel
19+
{
20+
Items =
21+
[
22+
new TocItemViewModel { Uid = "item1" },
23+
new TocItemViewModel { Uid = "item2" }
24+
],
25+
};
26+
27+
var yaml = ToYaml(item);
28+
var filePath = Path.Combine(Path.GetTempPath(), "toc.yml");
29+
File.WriteAllText(filePath, yaml, new UTF8Encoding(false));
30+
31+
try
32+
{
33+
// Act
34+
var result = TocHelper.LoadSingleToc(filePath);
35+
36+
// Assert
37+
result.Should().BeEquivalentTo(item);
38+
}
39+
finally
40+
{
41+
File.Delete(filePath);
42+
}
43+
}
44+
45+
[Fact]
46+
public void TestListDeserialization()
47+
{
48+
// Arrange
49+
var items = new TocItemViewModel[]
50+
{
51+
new TocItemViewModel { Uid = "item1" },
52+
new TocItemViewModel { Uid = "item2" },
53+
};
54+
55+
var yaml = ToYaml(items);
56+
var filePath = Path.Combine(Path.GetTempPath(), "toc.yml");
57+
File.WriteAllText(filePath, yaml);
58+
59+
try
60+
{
61+
// Act
62+
var result = TocHelper.LoadSingleToc(filePath);
63+
64+
// Assert
65+
result.Uid.Should().BeNull();
66+
result.Href.Should().BeNull();
67+
result.Items.Should().BeEquivalentTo(items);
68+
}
69+
finally
70+
{
71+
File.Delete(filePath);
72+
}
73+
}
74+
75+
[Fact]
76+
public void TestItemDeserializationWithEncoding()
77+
{
78+
// Arrange
79+
var item = new TocItemViewModel
80+
{
81+
Items =
82+
[
83+
new TocItemViewModel { Uid = "item1" },
84+
new TocItemViewModel { Uid = "item2" }
85+
],
86+
};
87+
88+
var yaml = ToYaml(item);
89+
90+
foreach (var encoding in Encodings)
91+
{
92+
var filePath = Path.Combine(Path.GetTempPath(), "toc.yml");
93+
File.WriteAllText(filePath, yaml, encoding);
94+
95+
try
96+
{
97+
// Act
98+
var result = TocHelper.LoadSingleToc(filePath);
99+
100+
// Assert
101+
result.Should().BeEquivalentTo(item);
102+
}
103+
finally
104+
{
105+
File.Delete(filePath);
106+
}
107+
}
108+
}
109+
110+
private static readonly Encoding[] Encodings =
111+
[
112+
new UTF8Encoding(false),
113+
new UTF8Encoding(true),
114+
Encoding.Unicode,
115+
Encoding.BigEndianUnicode,
116+
];
117+
118+
private static string ToYaml<T>(T model)
119+
{
120+
using StringWriter sw = new StringWriter();
121+
YamlUtility.Serialize(sw, model);
122+
return sw.ToString();
123+
}
124+
}

test/Docfx.Common.Tests/YamlDeserializerWithFallbackTest.cs

-71
This file was deleted.

0 commit comments

Comments
 (0)