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
  1. - word inside list->listItem->list->listItem->para.> + word inside list->listItem->list->listItem->para.> the second line.
  2. 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