.NET CIL Browser - Back to table of contents

Source file: HtmlGenerator.cs

Files in CilBrowser.Core directory:

AssemblyServer.cs

CilBrowser.Core.csproj

CilBrowserOptions.cs

FileUtils.cs

HtmlAttribute.cs

HtmlBuilder.cs

HtmlGenerator.cs

ServerBase.cs

SourceServer.cs

Utils.cs

WebsiteGenerator.cs

/* CIL Browser (https://github.com/MSDN-WhiteKnight/CilBrowser)
 * Copyright (c) 2022,  MSDN.WhiteKnight 
 * License: BSD 3-Clause */
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using CilBrowser.Core.SyntaxModel;
using CilTools.BytecodeAnalysis;
using CilTools.Reflection;
using CilTools.SourceCode.Common;
using CilTools.Syntax;

namespace CilBrowser.Core
{
    /// <summary>
    /// Visualizes CIL or source text as HTML in the context of the specified assembly
    /// </summary>
    public class HtmlGenerator
    {
        Assembly _ass; //could be null when unknown
        string _nsFilter; //could be empty string when there's no namespace filter
        string _customFooter; //could be empty string when there's no custom footer

        const string Footer = "Generated by <a href=\"https://github.com/MSDN-WhiteKnight/CilBrowser\">CIL Browser</a>";
        const string GlobalStyles = ".memberid { color: rgb(43, 145, 175); text-decoration: none; }";
        
        public HtmlGenerator()
        {
            this._ass = null;
            this._nsFilter = string.Empty;
            this._customFooter = string.Empty;
        }

        public HtmlGenerator(Assembly ass)
        {
            this._ass = ass;
            this._nsFilter = string.Empty;
            this._customFooter = string.Empty;
        }

        public HtmlGenerator(Assembly ass, string ns, string footer)
        {
            if (ns == null) ns = string.Empty;
            if (footer == null) footer = string.Empty;

            this._ass = ass;
            this._nsFilter = ns;
            this._customFooter = footer;
        }

        public string CustomFooter
        {
            get { return this._customFooter; }
            set { this._customFooter = value; }
        }
        
        static bool IsMethodInAssembly(MethodBase mb, Assembly ass)
        {
            if (mb == null || ass == null) return false;

            Type tDecl = mb.DeclaringType;

            return Utils.IsTypeInAssembly(tDecl, ass);
        }

        static bool IsNameToken(SyntaxNode node)
        {
            SourceToken tok = node as SourceToken;

            if (tok == null) return false;
            else return tok.Kind == TokenKind.Name;
        }

        void VisualizeNode(SyntaxNode node, HtmlBuilder target)
        {
            SyntaxNode[] children = node.GetChildNodes();
            HtmlAttribute[] attrs;

            if (children.Length > 0)
            {
                if (node is SyntaxElement)
                {
                    SyntaxElement elem = (SyntaxElement)node;

                    if (elem.Kind == SyntaxKind.TagStart || elem.Kind == SyntaxKind.TagEnd)
                    {
                        SyntaxNode[] tokens = elem.GetChildNodes();
                        int nameIndex;

                        if (elem.Kind == SyntaxKind.TagStart) nameIndex = 1;
                        else nameIndex = 2;

                        for (int i = 0; i < tokens.Length; i++)
                        {
                            if (i == nameIndex && IsNameToken(tokens[i]))
                            {
                                //highlighted tag name
                                target.WriteTag("span", tokens[i].ToString(),
                                    HtmlBuilder.OneAttribute("style", "color: blue;"));
                            }
                            else
                            {
                                //rest of the content
                                VisualizeNode(tokens[i], target);
                            }
                        }
                    }
                    else
                    {
                        foreach (SyntaxNode child in children) VisualizeNode(child, target);
                    }
                }
                else
                {
                    //other syntax nodes
                    foreach (SyntaxNode child in children) VisualizeNode(child, target);
                }
            }
            else if (node is KeywordSyntax)
            {
                KeywordSyntax ks = (KeywordSyntax)node;

                if (ks.Kind == KeywordKind.DirectiveName)
                {
                    attrs = new HtmlAttribute[1];
                    attrs[0] = new HtmlAttribute("style", "color: magenta;");
                }
                else if (ks.Kind == KeywordKind.Other)
                {
                    attrs = new HtmlAttribute[1];
                    attrs[0] = new HtmlAttribute("style", "color: blue;");
                }
                else attrs = new HtmlAttribute[0];

                target.WriteTag("span", node.ToString(), attrs);
            }
            else if (node is IdentifierSyntax)
            {
                IdentifierSyntax ids = (IdentifierSyntax)node;
                string tagName;
                List<HtmlAttribute> attrList = new List<HtmlAttribute>(5);

                if (ids.IsMemberName)
                {
                    if (ids.TargetMember is Type)
                    {
                        Type targetType = (Type)ids.TargetMember;

                        if (Utils.IsTypeInAssembly(targetType, this._ass) &&
                            Utils.IsMatchingNamespaceFilter(targetType, this._nsFilter))
                        {
                            //hyperlink for types in the same assembly
                            tagName = "a";
                            attrList.Add(new HtmlAttribute("href", GenerateTypeFileName(targetType)));
                        }
                        else
                        {
                            //plaintext name for other types
                            tagName = "span";
                        }
                    }
                    else if (ids.TargetMember is MethodBase)
                    {
                        MethodBase mb = (MethodBase)ids.TargetMember;

                        if (node.Parent is DirectiveSyntax &&
                            Utils.StrEquals(((DirectiveSyntax)node.Parent).Name, "method"))
                        {
                            //anchor when in method signature
                            tagName = "a";
                            attrList.Add(new HtmlAttribute("name", GenerateMethodAnchor(mb)));
                            attrList.Add(new HtmlAttribute("href", GenerateMethodURL(mb)));
                        }
                        else if (IsMethodInAssembly(mb, this._ass) &&
                            Utils.IsMatchingNamespaceFilter(mb.DeclaringType, this._nsFilter))
                        {
                            //hyperlink for methods in the same assembly
                            tagName = "a";
                            attrList.Add(new HtmlAttribute("href", GenerateMethodURL(mb)));
                        }
                        else
                        {
                            //plaintext name for other methods
                            tagName = "span";
                        }
                    }
                    else tagName = "span";

                    attrList.Add(new HtmlAttribute("class", "memberid"));
                }
                else tagName = "span";
                
                target.WriteTag(tagName, node.ToString(), attrList.ToArray());
            }
            else if (node is LiteralSyntax)
            {
                LiteralSyntax ls = (LiteralSyntax)node;

                if (ls.Value is string)
                {
                    attrs = new HtmlAttribute[1];
                    attrs[0] = new HtmlAttribute("style", "color: red;");
                }
                else attrs = new HtmlAttribute[0];

                target.WriteTag("span", node.ToString(), attrs);
            }
            else if (node is CommentSyntax)
            {
                attrs = new HtmlAttribute[1];
                attrs[0] = new HtmlAttribute("style", "color: green;");
                target.WriteTag("span", node.ToString(), attrs);
            }
            else if (node is SourceToken)
            {
                SourceToken token = (SourceToken)node;
                
                switch (token.Kind)
                {
                    case TokenKind.Keyword:
                        target.WriteTag("span", node.ToString(), HtmlBuilder.OneAttribute("style", "color: blue;"));
                        break;
                    case TokenKind.DoubleQuotLiteral:
                        target.WriteTag("span", node.ToString(), HtmlBuilder.OneAttribute("style", "color: red;"));
                        break;
                    case TokenKind.SingleQuotLiteral:
                        target.WriteTag("span", node.ToString(), HtmlBuilder.OneAttribute("style", "color: red;"));
                        break;
                    case TokenKind.SpecialTextLiteral:
                        target.WriteTag("span", node.ToString(), HtmlBuilder.OneAttribute("style", "color: red;"));
                        break;
                    case TokenKind.Comment:
                        target.WriteTag("span", node.ToString(), HtmlBuilder.OneAttribute("style", "color: green;"));
                        break;
                    case TokenKind.MultilineComment:
                        target.WriteTag("span", node.ToString(), HtmlBuilder.OneAttribute("style", "color: green;"));
                        break;
                    default:
                        target.WriteEscaped(node.ToString());
                        break;
                }
            }
            else
            {
                target.WriteEscaped(node.ToString());
            }
        }

        void WriteHeaderHTML(HtmlBuilder target)
        {
            target.StartParagraph();
            target.WriteEscaped(".NET CIL Browser");
            target.WriteRaw("&nbsp;-&nbsp;");

            if (this._ass != null)
            {
                target.WriteHyperlink("index.html", this._ass.GetName().Name);
            }
            else
            {
                target.WriteHyperlink("index.html", "Back to table of contents");
            }

            target.EndParagraph();
            target.WriteRaw(Environment.NewLine);
        }

        void WriteLayoutStart(HtmlBuilder target, string title, string navigation)
        {
            target.StartDocument(title, GlobalStyles);
            this.WriteHeaderHTML(target);
            target.WriteTag("h2", title);

            target.WriteTagStart("table", new HtmlAttribute[] {
                new HtmlAttribute("width", "100%"), new HtmlAttribute("cellpadding", "5"),
                new HtmlAttribute("cellspacing", "5")
            });

            target.WriteTagStart("tr");

            if (!string.IsNullOrEmpty(navigation))
            {
                target.WriteTagStart("td", new HtmlAttribute[] {
                    new HtmlAttribute("width", "200"), new HtmlAttribute("valign", "top"),
                    new HtmlAttribute("style", "border: thin solid;"),
                });

                target.WriteRaw(navigation);
                target.WriteTagEnd("td");
                target.WriteRaw(Environment.NewLine);
            }

            target.WriteTagStart("td", HtmlBuilder.OneAttribute("valign", "top"));
        }

        void WriteLayoutEnd(HtmlBuilder target)
        {
            target.WriteTagEnd("td");
            target.WriteTagEnd("tr");
            target.WriteTagEnd("table");

            target.StartParagraph();
            target.WriteHyperlink("index.html", "Back to table of contents");
            target.EndParagraph();

            WriteFooter(target);
            target.EndDocument();
        }
        
        public void VisualizeSyntaxNodes(IEnumerable<SyntaxNode> nodes, HtmlBuilder target)
        {
            target.WriteTagStart("pre", HtmlBuilder.OneAttribute("style", "white-space: pre-wrap;"));
            target.WriteTagStart("code");

            //content
            foreach (SyntaxNode node in nodes) this.VisualizeNode(node, target);

            target.WriteTagEnd("code");
            target.WriteTagEnd("pre");
        }

        public static string VisualizeMethod(MethodBase mb)
        {
            CilGraph gr = CilGraph.Create(mb);
            SyntaxNode[] nodes = new SyntaxNode[] { gr.ToSyntaxTree() };
            HtmlGenerator generator = new HtmlGenerator();
            StringBuilder sb = new StringBuilder(5000);
            HtmlBuilder builder = new HtmlBuilder(sb);

            generator.WriteLayoutStart(builder, "Method: " + mb.Name, string.Empty);
            generator.VisualizeSyntaxNodes(nodes, builder);
            generator.WriteLayoutEnd(builder);

            return sb.ToString();
        }

        static string VisualizeNavigationPanel(Type t, Dictionary<string, List<Type>> typeMap)
        {
            StringBuilder sb = new StringBuilder(1000);
            HtmlBuilder html = new HtmlBuilder(sb);            
            string ns = t.Namespace;
            List<Type> types;

            if (ns == null) ns = string.Empty;

            if (typeMap.ContainsKey(ns)) types = typeMap[ns];
            else types = new List<Type>();

            if (ns.Length > 0)
            {
                html.WriteParagraph("Types in " + ns + " namespace:");
            }
            else
            {
                html.WriteParagraph("Types without namespace:");
            }

            //list of types
            for (int i = 0; i < types.Count; i++)
            {
                html.StartParagraph();

                if (Utils.StrEquals(types[i].FullName, t.FullName))
                {
                    html.WriteTag("b", types[i].Name);
                }
                else
                {
                    html.WriteHyperlink(GenerateTypeFileName(types[i]), types[i].Name);
                }

                html.EndParagraph();
            }

            return sb.ToString();
        }
        
        public string VisualizeType(Type t, Dictionary<string, List<Type>> typeMap)
        {
            SyntaxNode[] nodes = SyntaxNode.GetTypeDefSyntax(t, true, new DisassemblerParams()).ToArray();

            if (nodes.Length == 0) return string.Empty;

            if (nodes.Length == 1)
            {
                if (string.IsNullOrWhiteSpace(nodes[0].ToString())) return string.Empty;
            }

            StringBuilder sb = new StringBuilder(5000);
            HtmlBuilder html = new HtmlBuilder(sb);

            string navigation = VisualizeNavigationPanel(t, typeMap);
            this.WriteLayoutStart(html, "Type: " + t.FullName, navigation);
            this.VisualizeSyntaxNodes(nodes, html);
            WriteLayoutEnd(html);

            return sb.ToString();
        }

        public string VisualizeAssemblyManifest(Assembly ass)
        {
            IEnumerable<SyntaxNode> nodes = Disassembler.GetAssemblyManifestSyntaxNodes(ass);
            StringBuilder sb = new StringBuilder(5000);
            HtmlBuilder html = new HtmlBuilder(sb);

            this.WriteLayoutStart(html, "Assembly: " + ass.GetName().Name, string.Empty);
            this.VisualizeSyntaxNodes(nodes, html);
            WriteLayoutEnd(html);

            return sb.ToString();
        }

        void VisualizeSourceTextImpl(string content, string ext, HtmlBuilder html)
        {
            //convert source text into tokens
            SyntaxNode[] nodes = SourceParser.Parse(content, ext);

            //convert tokens to HTML
            this.VisualizeSyntaxNodes(nodes, html);
        }

        public string VisualizeSourceFile(string content, string filename, string navigation, string sourceControlUrl)
        {
            string ext = Path.GetExtension(filename);
            StringBuilder sb = new StringBuilder(5000);
            HtmlBuilder html = new HtmlBuilder(sb);
            
            this.WriteLayoutStart(html, "Source file: " + filename, navigation);
            this.VisualizeSourceTextImpl(content, ext, html);

            if (!string.IsNullOrEmpty(sourceControlUrl))
            {
                html.WriteHyperlink(Utils.UrlAppend(sourceControlUrl, filename), "View in source control");
            }

            WriteLayoutEnd(html);

            return sb.ToString();
        }

        /// <summary>
        /// Generates HTML markup with syntax highlighting for the specified source text and writes it into TextWriter. 
        /// </summary>
        /// <param name="sourceText">Source text to render</param>
        /// <param name="ext">
        /// Source file extension (with leading dot) that defines programming language. For example, <c>.cs</c> for C#.
        /// </param>
        /// <param name="target">Text writer where to output the resulting HTML</param>
        public static void RenderSourceText(string sourceText, string ext, TextWriter target)
        {
            if (string.IsNullOrEmpty(sourceText)) return;

            if (target == null) throw new ArgumentNullException("target");

            HtmlGenerator generator = new HtmlGenerator();
            HtmlBuilder builder = new HtmlBuilder(target);
            generator.VisualizeSourceTextImpl(sourceText, ext, builder);
            target.Flush();
        }

        /// <summary>
        /// Generates HTML markup with syntax highlighting for the specified source text.
        /// </summary>
        /// <param name="sourceText">Source text to render</param>
        /// <param name="ext">
        /// Source file extension (with leading dot) that defines programming language. For example, <c>.cs</c> for C#.
        /// </param>
        public static string RenderSourceText(string sourceText, string ext)
        {
            if (string.IsNullOrEmpty(sourceText)) return string.Empty;

            StringBuilder sb = new StringBuilder(sourceText.Length * 2);
            StringWriter wr = new StringWriter(sb);
            HtmlGenerator generator = new HtmlGenerator();
            HtmlBuilder builder = new HtmlBuilder(wr);
            generator.VisualizeSourceTextImpl(sourceText, ext, builder);
            return sb.ToString();
        }

        /// <summary>
        /// Writes CSS code containing syntax highlighting styles into the specified TextWriter. 
        /// These styles are used in markup generated by <see cref="RenderSourceText"/> method.
        /// </summary>
        /// <param name="target">Text writer where to output the resulting CSS</param>
        public static void GetVisualStyles(TextWriter target)
        {
            if (target == null) throw new ArgumentNullException("target");

            target.WriteLine(GlobalStyles);
            target.Flush();
        }

        /// <summary>
        /// Gets CSS code containing syntax highlighting styles. These styles are used in markup generated by 
        /// <see cref="RenderSourceText"/> method.
        /// </summary>
        public static string GetVisualStyles()
        {
            return GlobalStyles;
        }

        public static string GenerateTypeFileName(Type t)
        {
            return GenerateTypeFileNameShort(t) + ".html";
        }

        public static string GenerateTypeFileNameShort(Type t)
        {
            return ((uint)t.MetadataToken).ToString("X", CultureInfo.InvariantCulture);
        }

        static string GenerateMethodAnchor(MethodBase mb)
        {
            return "M"+((uint)mb.MetadataToken).ToString("X", CultureInfo.InvariantCulture);
        }

        static string GenerateMethodURL(MethodBase mb)
        {
            if (mb.DeclaringType == null) return string.Empty;

            MethodBase targetMethod = mb;

            if (mb.IsGenericMethod && !mb.IsGenericMethodDefinition && mb is ICustomMethod)
            {
                // if it's an instantiated generic method, we want to use method definition as link target
                ICustomMethod cm = (ICustomMethod)mb;

                if (cm.GetDefinition() != null) targetMethod = cm.GetDefinition();
            }

            return GenerateTypeFileName(mb.DeclaringType) + "#" + GenerateMethodAnchor(targetMethod);
        }
        
        public void WriteFooter(HtmlBuilder target)
        {
            target.WriteTag("hr", string.Empty);

            if (!string.IsNullOrEmpty(this._customFooter))
            {
                target.StartParagraph();
                target.WriteRaw(this._customFooter);
                target.EndParagraph();
            }

            target.StartParagraph();
            target.WriteRaw(Footer);
            target.EndParagraph();
        }
        
        public static string VisualizeException(Exception ex)
        {
            StringBuilder sb = new StringBuilder(500);
            sb.Append("<pre>");
            sb.Append(WebUtility.HtmlEncode(ex.GetType().ToString()));
            sb.Append(": ");
            sb.Append(WebUtility.HtmlEncode(ex.Message));
            sb.Append("</pre>");
            return sb.ToString();
        }

        static void VisualizeVersionInfo(HtmlBuilder target, Assembly ass)
        {
            AssemblyName an = ass.GetName();
            target.WriteTagStart("table", HtmlBuilder.OneAttribute("cellpadding", "4"));

            if (an.Version != null)
            {
                target.WriteTagStart("tr");
                target.WriteTag("td", "Assembly version");
                target.WriteTag("td", an.Version.ToString());
                target.WriteTagEnd("tr");
            }

            if (string.IsNullOrEmpty(ass.Location))
            {
                //cannot display version info if location is unknown
                target.WriteTagEnd("table");
                return;
            }

            FileVersionInfo fvi;

            try
            {
                fvi = FileVersionInfo.GetVersionInfo(ass.Location);
            }
            catch (Exception ex)
            {
                target.WriteTagEnd("table");
                target.WriteParagraph("Failed to get file version info!");
                target.WriteRaw(VisualizeException(ex));                
                Console.WriteLine(ex.ToString());
                return;
            }

            if (fvi.FileVersion != null)
            {
                target.WriteTagStart("tr");
                target.WriteTag("td", "File version");
                target.WriteTag("td", fvi.FileVersion);
                target.WriteTagEnd("tr");
            }

            if (fvi.ProductVersion != null)
            {
                target.WriteTagStart("tr");
                target.WriteTag("td", "Product version");
                target.WriteTag("td", fvi.ProductVersion);
                target.WriteTagEnd("tr");
            }

            if (fvi.ProductName != null)
            {
                target.WriteTagStart("tr");
                target.WriteTag("td", "Product name");
                target.WriteTag("td", fvi.ProductName);
                target.WriteTagEnd("tr");
            }

            if (fvi.CompanyName != null)
            {
                target.WriteTagStart("tr");
                target.WriteTag("td", "Vendor");
                target.WriteTag("td", fvi.CompanyName);
                target.WriteTagEnd("tr");
            }

            target.WriteTagEnd("table");
            
            if (!string.IsNullOrEmpty(fvi.LegalCopyright)) target.WriteParagraph(fvi.LegalCopyright);
        }

        public static void WriteTocStart(HtmlBuilder toc, Assembly ass)
        {
            AssemblyName an = ass.GetName();
            toc.StartDocument(".NET CIL Browser - " + an.Name, GlobalStyles);
            toc.WriteParagraph(".NET CIL Browser");
            toc.WriteTag("h1", an.Name);
            VisualizeVersionInfo(toc, ass);
            toc.WriteRaw(Environment.NewLine);
            toc.StartParagraph();
            toc.WriteHyperlink("assembly.html", "(Assembly manifest)");
            toc.EndParagraph();
            toc.WriteParagraph("Types in assembly: ");
        }

        public static void WriteTocStart(HtmlBuilder toc, string dirName)
        {
            toc.StartDocument(".NET CIL Browser - " + dirName, GlobalStyles);
            toc.WriteParagraph(".NET CIL Browser");
            toc.WriteTag("h1", "Source directory: " + dirName);
            toc.WriteRaw(Environment.NewLine);
        }
    }
}
View in source control

Back to table of contents


Generated by CIL Browser