diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/Parsers/TripleSlashCommentModel.cs b/src/Microsoft.DocAsCode.Metadata.ManagedReference/Parsers/TripleSlashCommentModel.cs
index 21386b40d2a..a9793d21138 100644
--- a/src/Microsoft.DocAsCode.Metadata.ManagedReference/Parsers/TripleSlashCommentModel.cs
+++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/Parsers/TripleSlashCommentModel.cs
@@ -556,7 +556,7 @@ private string GetXmlValue(XPathNavigator node)
var lineInfo = node as IXmlLineInfo;
int column = lineInfo.HasLineInfo() ? lineInfo.LinePosition - 2 : 0;
- return NormalizeXml(RemoveLeadingSpaces(node.InnerXml), column);
+ return NormalizeXml(RemoveLeadingSpaces(GetInnerXml(node)), column);
}
///
@@ -644,5 +644,45 @@ private static string NormalizeXml(string xml, int parentIndex)
return m.Value.Replace(group.ToString(), group.ToString().Trim('\n'));
});
}
+
+ ///
+ /// `>` is always encoded to `>` in XML, when triple-slash-comments is considered as Markdown content, `>` is considered as blockquote
+ /// Decode `>` to enable the Markdown syntax considering `>` is not a Must-Encode in Text XElement
+ ///
+ ///
+ ///
+ private static string GetInnerXml(XPathNavigator node)
+ {
+ using (var sw = new MemoryStream())
+ {
+ using (var tw = new XmlWriterWithGtDecoded(sw, Encoding.UTF8))
+ {
+ if (node.MoveToFirstChild())
+ {
+ do
+ {
+ tw.WriteNode(node, true);
+ } while (node.MoveToNext());
+ node.MoveToParent();
+ }
+ tw.Flush();
+ sw.Position = 0;
+ using (var sr = new StreamReader(sw))
+ {
+ return sr.ReadToEnd();
+ }
+ }
+ }
+ }
+
+ private sealed class XmlWriterWithGtDecoded : XmlTextWriter
+ {
+ public XmlWriterWithGtDecoded(Stream w, Encoding encoding) : base(w, encoding) { }
+ public override void WriteString(string text)
+ {
+ var encoded = text.Replace("&", "&").Replace("<", "<").Replace("'", "'").Replace("\"", """);
+ base.WriteRaw(encoded);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/docfx/Models/FileMapping.cs b/src/docfx/Models/FileMapping.cs
index 253a34f2b72..34276f67c47 100644
--- a/src/docfx/Models/FileMapping.cs
+++ b/src/docfx/Models/FileMapping.cs
@@ -25,6 +25,9 @@ namespace Microsoft.DocAsCode
/// 2. Compact form
/// This form supports multiple file patterns in an array
/// e.g. `projects: ["file1", "file2"]`
+ /// 2. Object form
+ /// If the Array form contains only one item, it can be shortened to an object
+ /// e.g. `projects: ["file1", "file2"]`
///
[JsonConverter(typeof(FileMappingConverter))]
[Serializable]
diff --git a/src/docfx/Models/FileMappingConverter.cs b/src/docfx/Models/FileMappingConverter.cs
index af713a9462d..1b49bcd6f59 100644
--- a/src/docfx/Models/FileMappingConverter.cs
+++ b/src/docfx/Models/FileMappingConverter.cs
@@ -25,10 +25,6 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
{
jItems = JArray.Load(reader);
}
- else if (reader.TokenType == JsonToken.StartObject)
- {
- jItems = JContainer.Load(reader);
- }
else if (reader.TokenType == JsonToken.String)
{
jItems = JRaw.Load(reader);
@@ -42,6 +38,10 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
{
model.Add(FileModelParser.ParseItem(jItems.ToString()));
}
+ else if (jItems is JObject)
+ {
+ model.Add(FileModelParser.ParseItem((JToken)jItems));
+ }
else
{
foreach (var item in jItems)
diff --git a/src/docfx/Program.cs b/src/docfx/Program.cs
index 770ad7518e4..3c469c2eea2 100644
--- a/src/docfx/Program.cs
+++ b/src/docfx/Program.cs
@@ -27,7 +27,7 @@ static int Main(string[] args)
}
}
- private static int ExecSubCommand(string[] args)
+ internal static int ExecSubCommand(string[] args)
{
var consoleLogListener = new ConsoleLogListener();
var replayListener = new ReplayLogListener();
diff --git a/test/Microsoft.DocAsCode.Metadata.ManagedReference.Tests/TripleSlashParserUnitTest.cs b/test/Microsoft.DocAsCode.Metadata.ManagedReference.Tests/TripleSlashParserUnitTest.cs
index db48da038d9..a98351c4e9a 100644
--- a/test/Microsoft.DocAsCode.Metadata.ManagedReference.Tests/TripleSlashParserUnitTest.cs
+++ b/test/Microsoft.DocAsCode.Metadata.ManagedReference.Tests/TripleSlashParserUnitTest.cs
@@ -156,7 +156,7 @@ Classes in assemblies are by definition complete.
public class XmlElement
: XmlLinkedNode
-
- word inside list->listItem->list->listItem->para.>
+ word inside list->listItem->list->listItem->para.>
the second line.
- item2 in numbered list
item2 in bullet list
diff --git a/test/docfx.Tests/CompositeCommandTest.cs b/test/docfx.Tests/CompositeCommandTest.cs
new file mode 100644
index 00000000000..b95dd34184d
--- /dev/null
+++ b/test/docfx.Tests/CompositeCommandTest.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.DocAsCode.Tests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+
+ using Xunit;
+
+ using Microsoft.DocAsCode.Common;
+ using Microsoft.DocAsCode.DataContracts.Common;
+ using Microsoft.DocAsCode.DataContracts.ManagedReference;
+ using Microsoft.DocAsCode.SubCommands;
+ using Microsoft.DocAsCode.Tests.Common;
+ using HtmlAgilityPack;
+
+ [Collection("docfx STA")]
+ public class CompositeCommandTest : TestBase
+ {
+ ///
+ /// Use MetadataCommand to generate YAML files from a c# project and a VB project separately
+ ///
+ private string _outputFolder;
+ private string _projectFolder;
+
+ public CompositeCommandTest()
+ {
+ _outputFolder = Path.GetFullPath(GetRandomFolder());
+ _projectFolder = Path.GetFullPath(GetRandomFolder());
+ }
+
+ [Fact]
+ [Trait("Related", "docfx#428")]
+ [Trait("Language", "CSharp")]
+ public void TestCommandFromCSCodeToHtml()
+ {
+ // Create source file
+ var sourceCode = @"
+public namespace Hello{
+///
+/// The class < > > description goes here...
+///
+///
+/// Here is some < encoded > example...
+/// > [!NOTE]
+/// > This is *note*
+///
+///
+/// var handler = DateTimeHandler();
+/// for (var i = 0; i < 10; i++){
+/// date = date.AddMonths(1);
+/// }
+///
+///
+public class HelloWorld(){}}
+";
+ var sourceFile = Path.Combine(_projectFolder, "test.cs");
+ File.WriteAllText(sourceFile, sourceCode);
+
+ var docfxJson = $@"{{
+""metadata"": [
+ {{
+ ""src"": ""test.cs"",
+ ""cwd"": ""{_projectFolder.ToNormalizedPath()}"",
+ ""dest"": ""{_outputFolder.ToNormalizedPath()}/api""
+ }}
+],
+""build"": {{
+ ""content"": {{
+ ""files"": ""api/*.yml"",
+ ""cwd"": ""../{Path.GetFileName(_outputFolder)}""
+ }},
+ ""dest"": ""{_outputFolder.ToNormalizedPath()}/site""
+}}
+}}";
+ var docfxJsonFile = Path.Combine(_projectFolder, "docfx.json");
+ File.WriteAllText(docfxJsonFile, docfxJson);
+ Program.ExecSubCommand(new string[] { docfxJsonFile });
+ var filePath = Path.Combine(_outputFolder, "site", "api", "Hello.HelloWorld.html");
+ Assert.True(File.Exists(filePath));
+ var html = new HtmlDocument();
+ html.Load(filePath);
+ var summary = html.DocumentNode.SelectSingleNode("//div[contains(@class, 'summary')]/p").InnerHtml;
+ Assert.Equal("The class < > > description goes here...", summary.Trim());
+ var note = html.DocumentNode.SelectSingleNode("//div[@class='NOTE']").InnerHtml;
+ Assert.Equal("Note
This is note
", note.Trim());
+ var code = html.DocumentNode.SelectNodes("//pre/code")[1].InnerHtml;
+ Assert.Equal(@"var handler = DateTimeHandler();
+for (var i = 0; i < 10; i++){
+ date = date.AddMonths(1);
+}".Replace("\r\n", "\n"), code);
+ }
+ }
+}
\ No newline at end of file