// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Xml; using dnSpy.Contracts.Text; namespace dnSpy.Contracts.Decompiler.XmlDoc { /// /// XML doc output /// public interface IXmlDocOutput { /// /// Writes a new line /// void WriteNewLine(); /// /// Writes a space character /// void WriteSpace(); /// /// Writes text /// /// Text /// Data void Write(string s, object data); } /// /// Renders XML documentation /// public class XmlDocRenderer : IXmlDocOutput { readonly StringBuilder ret = new StringBuilder(); void IXmlDocOutput.WriteNewLine() => ret.AppendLine(); void IXmlDocOutput.WriteSpace() => ret.Append(' '); void IXmlDocOutput.Write(string s, object data) => ret.Append(s); /// /// Appends text /// /// Text public void AppendText(string text) => ret.Append(text); /// /// Adds xml documentation /// /// XML documentation public void AddXmlDocumentation(string xmlDocumentation) => WriteXmlDoc(this, xmlDocumentation); /// /// Writes XML documentation /// /// Output /// XML documentation /// public static bool WriteXmlDoc(IXmlDocOutput output, string? xmlDocumentation) { if (xmlDocumentation is null) return false; try { XmlTextReader r = new XmlTextReader(new StringReader("" + xmlDocumentation + "")); r.XmlResolver = null; AddXmlDocumentation(output, r); } catch (XmlException) { } return true; } /// /// Whitespace regex /// public static Regex WhitespaceRegex => whitespace; static readonly Regex whitespace = new Regex(@"\s+"); static void AddXmlDocumentation(IXmlDocOutput output, XmlReader xml) { bool isNewLine = true; while (xml.Read()) { if (xml.NodeType == XmlNodeType.Element) { string elname = xml.Name.ToLowerInvariant(); switch (elname) { case "filterpriority": case "remarks": xml.Skip(); break; case "example": output.WriteNewLine(); output.Write("Example", BoxedTextColor.XmlDocToolTipHeader); output.Write(":", BoxedTextColor.Text); output.WriteNewLine(); isNewLine = true; break; case "exception": output.WriteNewLine(); output.Write(GetCref(xml["cref"]), BoxedTextColor.XmlDocToolTipHeader); output.Write(":", BoxedTextColor.Text); output.WriteSpace(); isNewLine = false; break; case "returns": output.WriteNewLine(); output.Write("Returns", BoxedTextColor.XmlDocToolTipHeader); output.Write(":", BoxedTextColor.Text); output.WriteSpace(); isNewLine = false; break; case "see": output.Write(GetCref(xml["cref"]), BoxedTextColor.Text); output.Write((xml["langword"] ?? string.Empty).Trim(), BoxedTextColor.Keyword); isNewLine = false; break; case "seealso": output.WriteNewLine(); output.Write("See also", BoxedTextColor.XmlDocToolTipHeader); output.Write(":", BoxedTextColor.Text); output.WriteSpace(); output.Write(GetCref(xml["cref"]), BoxedTextColor.Text); isNewLine = false; break; case "paramref": output.Write((xml["name"] ?? string.Empty).Trim(), BoxedTextColor.Parameter); isNewLine = false; break; case "param": output.WriteNewLine(); output.Write(whitespace.Replace((xml["name"] ?? string.Empty).Trim(), " "), BoxedTextColor.Parameter); output.Write(":", BoxedTextColor.Text); output.WriteSpace(); isNewLine = false; break; case "typeparam": output.WriteNewLine(); output.Write(whitespace.Replace((xml["name"] ?? string.Empty).Trim(), " "), BoxedTextColor.TypeGenericParameter); output.Write(":", BoxedTextColor.Text); output.WriteSpace(); isNewLine = false; break; case "value": output.WriteNewLine(); output.Write("Value", BoxedTextColor.Keyword); output.Write(":", BoxedTextColor.Text); output.WriteNewLine(); isNewLine = true; break; case "br": case "para": output.WriteNewLine(); isNewLine = true; break; default: break; } } else if (xml.NodeType == XmlNodeType.Text) { var s = whitespace.Replace(xml.Value, " "); if (isNewLine) s = s.TrimStart(); output.Write(s, BoxedTextColor.Text); isNewLine = false; } } } /// /// Gets a cref /// /// /// public static string GetCref(string? cref) { if (string2.IsNullOrWhiteSpace(cref)) return string.Empty; if (cref.Length < 2) { return cref.Trim(); } if (cref.Substring(1, 1) == ":") { return cref.Substring(2, cref.Length - 2).Trim(); } return cref.Trim(); } /// public override string ToString() => ret.ToString(); } }