CodeViewer.cs

// Copyright � 2007 Jeffrey Bazinet, http://www.vwd-cms.com/

using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Text.RegularExpressions;
using System.Text;
using System.Collections;
using System;
using System.IO;
using VwdCms.Html;
using System.Collections.Specialized;

namespace VwdCms
{
    public class CodeViewer : System.Web.UI.WebControls.Panel
    {
        public enum CodeLanguages
        {
            Html,
            CSharp
        }
        public enum CodeSources
        {
            File,
            Text
        }

        private const string fileHeader = "\r\n<div style=\"background-color:lightsteelblue;"
            + "font-weight:bold;width:98%;padding-left:2px;\"> {0}</div>\r\n";

        private string _codeFile;
        private CodeLanguages _codeLanguage = CodeLanguages.Html;
        private CodeSources _codeSource = CodeSources.Text;
        private bool _codeIsHtmlEncoded = false;
        private bool _showCodeFileHeader = true;

        private Hashtable _htExtensions = null;

        private string _csharpColorDefault = ConfigurationManager.AppSettings["CSharpColorDefault"];
        private string _csharpColorStrings = ConfigurationManager.AppSettings["CSharpColorStrings"];
        private string _csharpColorKeywords = ConfigurationManager.AppSettings["CSharpColorKeywords"];
        private string _csharpColorComments = ConfigurationManager.AppSettings["CSharpColorComments"];

        private string _htmlColorDefault = ConfigurationManager.AppSettings["HtmlColorDefault"];
        private string _htmlColorTagNames = ConfigurationManager.AppSettings["HtmlColorTagNames"];
        private string _htmlColorComments = ConfigurationManager.AppSettings["HtmlColorComments"];
        private string _htmlColorAttributeNames = ConfigurationManager.AppSettings["HtmlColorAttributeNames"];
        private string _htmlColorAttributeValues = ConfigurationManager.AppSettings["HtmlColorAttributeValues"];
        private string _htmlColorAspCodeTags = ConfigurationManager.AppSettings["HtmlColorAspCodeTags"];
        private string _htmlColorAspCode = ConfigurationManager.AppSettings["HtmlColorAspCode"];
        private string _htmlColorText = ConfigurationManager.AppSettings["HtmlColorText"];
        private string _htmlColorScriptCode = ConfigurationManager.AppSettings["HtmlColorScriptCode"];

        private StringCollection _scKeywords = null;
        private bool _codeRendered = false;

        public StringCollection Keywords
        {
            get { return _scKeywords; }
        }

        protected override void OnLoad(EventArgs e)
        {
            RenderCode();
            base.OnLoad(e);
        }

        public void RenderCode()
        {
            if (!_codeRendered)
            {
                _codeRendered = true;

                StringBuilder sbCode = new StringBuilder();
                string startTag = "<pre style=\"color:{0};\">";
                string endTag = "</pre>";
                string defaultColor = null;
                string codelanguage = null;
                string code = null;

                // determine the source and load the code
                if (this.CodeSource == CodeSources.File  !string.IsNullOrEmpty(this.CodeFile))
                {
                    StreamReader sr = null;

                    string filename = this.CodeFile;
                    int index = filename.LastIndexOf('\\');
                    if (index != -1)
                    {
                        filename = filename.Substring(index + 1);
                    }
                    filename = filename.Replace(".exclude", string.Empty);

                    this.LoadFileExenstions();
                    string extension = this.GetExtension(filename);
                    codelanguage = Convert.ToString(_htExtensions[extension]);

                    if (this.ShowCodeFileHeader)
                    {
                        sbCode.Append(string.Format(fileHeader, filename));
                    }

                    try
                    {
                        sr = new StreamReader(this.CodeFile);
                        code = sr.ReadToEnd();
                    }
                    finally
                    {
                        if (sr != null)
                        {
                            sr.Close();
                            sr = null;
                        }
                    }
                }
                else if (this.CodeSource == CodeSources.Text)
                {
                    if (this.CodeLanguage == CodeLanguages.CSharp)
                    {
                        codelanguage = "csharp";
                    }
                    else if (this.CodeLanguage == CodeLanguages.Html)
                    {
                        codelanguage = "html";
                    }

                    code = this.Text;
                }

                // process and render the code
                if (code != null  codelanguage != null)
                {
                    if (this.CodeIsHtmlEncoded)
                    {
                        code = HttpUtility.HtmlDecode(code);
                    }

                    if (codelanguage == "csharp")
                    {
                        defaultColor = _csharpColorDefault;
                        code = FormatCSharpCode(code);
                    }
                    else if (codelanguage == "html")
                    {
                        defaultColor = _htmlColorDefault;
                        code = FormatHtmlCode(code);
                    }
                }

                // add the formatted code to the control
                this.Controls.Clear();
                if (code != null)
                {
                    sbCode.Append(string.Format(startTag, defaultColor));
                    sbCode.Append(code);
                    sbCode.Append(endTag);
                    sbCode.Replace("\t", "    ");

                    this.Controls.Add(new LiteralControl(sbCode.ToString()));
                }
            }
        }

        public string Text
        {
            get
            {
                string text = Convert.ToString(this.ViewState["text"]);
                if (string.IsNullOrEmpty(text))
                {
                    if (this.IsLiteralContent())
                    {
                        StringBuilder sbText = new StringBuilder();
                        foreach (LiteralControl lit in this.Controls)
                        {
                            sbText.Append(lit.Text);
                        }
                        text = sbText.ToString();
                        this.ViewState["text"] = text;
                    }
                }
                return text;
            }
            set { this.ViewState["text"] = value; }
        }

        public CodeSources CodeSource
        {
            get { return _codeSource; }
            set { _codeSource = value; }
        }

        public CodeLanguages CodeLanguage
        {
            get { return _codeLanguage; }
            set { _codeLanguage = value; }
        }

        public string CodeFile
        {
            get { return _codeFile; }
            set { _codeFile = value; }
        }

        public bool CodeIsHtmlEncoded
        {
            get { return _codeIsHtmlEncoded; }
            set { _codeIsHtmlEncoded = value; }
        }

        public bool ShowCodeFileHeader
        {
            get { return _showCodeFileHeader; }
            set { _showCodeFileHeader = value; }
        }

        private void LoadFileExenstions()
        {
            string csharp = System.Configuration.ConfigurationManager.AppSettings["CSharpParserExtensions"];
            string html = System.Configuration.ConfigurationManager.AppSettings["HtmlParserExtensions"];

            _htExtensions = new Hashtable();

            string[] extensions = null;
            
            extensions = csharp.Split(';');
            if (extensions != null  extensions.Length  0)
            {
                foreach (string ext in extensions)
                {
                    if (_htExtensions[ext] == null)
                    {
                        _htExtensions.Add(ext, "csharp");
                    }
                }
            }
            extensions = html.Split(';');
            if (extensions != null  extensions.Length  0)
            {
                foreach (string ext in extensions)
                {
                    if (_htExtensions[ext] == null)
                    {
                        _htExtensions.Add(ext, "html");
                    }
                }
            }
        }


        private string GetExtension(string filename)
        {
            string extension = string.Empty;
            if (filename != null)
            {
                int index = filename.LastIndexOf(".");
                if (index != -1)
                {
                    extension = filename.Substring(index);
                }
            }

            return extension.ToLower();
        }

        public string FormatHtmlCode(string source)
        {
            Html.HtmlParser parser = new HtmlParser();

            HtmlTagCollection tags = parser.GetHtmlTags(source);
            HtmlNode docNode = parser.GetDocumentNode(tags);

            StringBuilder sbOut = new StringBuilder();

            this.ProcessNode(docNode, sbOut);

            return sbOut.ToString();
        }

        private void ProcessNode(HtmlNode node, StringBuilder sbCode)
        {
            string outerHtml = null;
            StringBuilder sbTagFormat = new StringBuilder(node.TagFormat);

            switch (node.NodeType)
            {
                case HtmlNodeTypes.AspCodeBlock:
                    outerHtml = node.Text.Substring(2, node.Text.Length-4);
                    outerHtml = HttpUtility.HtmlEncode(outerHtml);

                    sbCode.Append("<span style=\"background-color:" 
                        + _htmlColorAspCodeTags + ";\">&lt;%</span>");
                    sbCode.Append("<span style=\"color:" + _htmlColorAspCode + ";\">");
                    sbCode.Append(outerHtml);
                    sbCode.Append("</span>");
                    sbCode.Append("<span style=\"background-color:" 
                        + _htmlColorAspCodeTags + ";\">%&gt;</span>");

                    break;
                case HtmlNodeTypes.AspDirective:
                    ProcessAttributes(node, sbTagFormat);

                    sbTagFormat.Remove(0, node.StartsWith.Length);
                    sbTagFormat.Insert(0, "<span style=\"background-color:" 
                        + _htmlColorAspCodeTags + ";\">&lt;%</span>@");

                    sbTagFormat.Replace("{tagname}", "<span style=\"color:" 
                        + _htmlColorTagNames + ";\">" + node.TagName + "</span>");
                    
                    sbTagFormat.Remove(sbTagFormat.Length - node.EndsWith.Length, node.EndsWith.Length);
                    sbTagFormat.Append("<span style=\"background-color:" 
                        + _htmlColorAspCodeTags + ";\">%&gt;</span>");

                    sbCode.Append(sbTagFormat.ToString());
                    break;
                case HtmlNodeTypes.AspComment:
                case HtmlNodeTypes.Comment:
                    outerHtml = node.OuterHtml;
                    outerHtml = HttpUtility.HtmlEncode(outerHtml);

                    sbCode.Append("<span style=\"color:" + _htmlColorComments + ";\">");
                    sbCode.Append(outerHtml);
                    sbCode.Append("</span>");
                    break;
                case HtmlNodeTypes.Document:
                    // do nothing
                    break;
                case HtmlNodeTypes.DocType:
                    sbTagFormat = new StringBuilder(HttpUtility.HtmlEncode(sbTagFormat.ToString()));
                    ProcessAttributes(node, sbTagFormat);

                    sbTagFormat.Replace("{tagname}", "<span style=\"color:" 
                        + _htmlColorTagNames + ";\">" + node.TagName + "</span>");

                    sbCode.Append(sbTagFormat.ToString());

                    break;
                case HtmlNodeTypes.Element:
                    ProcessAttributes(node, sbTagFormat);

                    sbTagFormat.Replace("{tagname}", "<span style=\"color:" 
                        + _htmlColorTagNames + ";\">" + node.TagName + "</span>");

                    sbCode.Append(sbTagFormat.ToString());
                    
                    break;
                case HtmlNodeTypes.XmlDocumentDeclaration:
                    sbTagFormat = new StringBuilder(HttpUtility.HtmlEncode(sbTagFormat.ToString()));
                    ProcessAttributes(node, sbTagFormat);
                    sbTagFormat.Replace("{tagname}", "<span style=\"color:" 
                        + _htmlColorTagNames + ";\">xml</span>");

                    sbCode.Append(sbTagFormat.ToString());

                    break;
                case HtmlNodeTypes.Literal:
                    if (node.ParentNode.TagName.ToLower() == "script")
                    {
                        sbCode.Append("<span style=\"color:" + _htmlColorScriptCode + ";\">");
                    }
                    else
                    {
                        sbCode.Append("<span style=\"color:" + _htmlColorText + ";\">");
                    } 
                    sbCode.Append(FormatText(node.Text));
                    sbCode.Append("</span>");
                    break;
            }

            if ( node.HasChildren )
            {
                foreach (HtmlNode child in node.ChildNodes)
                {
                    ProcessNode(child, sbCode);                
                }
            }

            if ( node.NodeType == HtmlNodeTypes.Element  !node.SelfClosing)
            {
                string endTag = HttpUtility.HtmlEncode(node.EndTagFormat);
                if (string.IsNullOrEmpty(endTag))
                {
                    endTag = "</{tagname}>";
                }
                endTag = endTag.Replace("{tagname}", "<span style=\"color:" 
                    + _htmlColorTagNames + ";\">" + node.TagName + "</span>");
                sbCode.Append(endTag);
            }
        }

        private string FormatText(string text)
        {
            StringBuilder sb = new StringBuilder(text);
            sb.Replace("\r\n", "<br/>");
            sb.Replace("\r", "<br/>");
            sb.Replace("\n", "<br/>");
            sb.Replace("\t", "    ");
            return sb.ToString();
        }

        private void ProcessAttributes(HtmlNode node, StringBuilder sbTagFormat)
        {
            HtmlAttribute attrib = null;
            int index = 0;
            string name = null;
            string val = null;

            if ( node.HasAttributes )
            {
                for ( index = 0; index  node.Attributes.Count; index++ )
                {
                    attrib = node.Attributes[index];

                    if (attrib.Name != null  attrib.Value != null )
                    {
                        name = "<span style=\"color:" + _htmlColorAttributeNames 
                            + ";\">" + attrib.Name + "</span>";
                        sbTagFormat.Replace("{name" + index.ToString() + "}", name);

                        val = "<span style=\"color:" + _htmlColorAttributeValues 
                            + ";\">" + attrib.Value + "</span>";
                        sbTagFormat.Replace("{val" + index.ToString() + "}", val);
                    }
                    else if ( attrib.Name != null  )
                    {
                        name = "<span style=\"color:" + _htmlColorAttributeNames 
                            + ";\">" + attrib.Name + "</span>";
                        sbTagFormat.Replace("{name" + index.ToString() + "}", name);
                    }
                    else if ( attrib.Value != null )
                    {
                        val = "<span style=\"color:" + _htmlColorAttributeValues 
                            + ";\">" + attrib.Value + "</span>";
                        sbTagFormat.Replace("{val" + index.ToString() + "}", val);
                    }
                }
            }
        }

        public string FormatCSharpCode(string source)
        {
            Hashtable htKeywords = GetCSharpKeywords();

            string reComments = @"(/\*.*(\*/){0}\*/){1}|(//[^\n]*\n){1}";
            string reStrings = @"(@""(("""")|[^""])*"")|(""((\\"")|[^""])*"")|((""[\""]*[^""]*"")+|(""{2})+)+";
            string reChars = @"('[^']*')+";
            string reStuff = @"[!,@:%; \t\r\n\.\$\^\{\}\[\]\(\)\|\*\+\?\=\\]{1}";
            string reMinuses = @"(-){1}";
            string reDivisions = @"((/){1})+";
            string reEncodings = @"(&\w+;)+";
            string reWords = @"([\w+\.*])+";

            string reQuery = "(" + reComments + "|" + reStrings + "|"
                + reChars + "|" + reStuff + "|" + reMinuses + "|"
                + reDivisions + "|" + reEncodings + "|" + reWords + ")";

            Regex regexCode = new Regex(reQuery, RegexOptions.Singleline);

            StringBuilder sbOut = new StringBuilder();

            MatchCollection wordMatches = null;

            // get all individual words/characters in the source
            wordMatches = regexCode.Matches(source);

            string text = null;
            string[] words = null;
            string word = null;
            string dot = null;

            foreach (Match wordMatch in wordMatches)
            {
                text = wordMatch.Value;
                if ( ( text.StartsWith("@\"") || text.StartsWith("\""))  text.EndsWith("\"")) // it's a string
                {
                    text = "<span style=\"color:" + _csharpColorStrings 
                        + ";\">" + HttpUtility.HtmlEncode(text) + "</span>";
                    sbOut.Append(text);
                }
                else if (text.StartsWith("//") || text.StartsWith("/*")) // it's a comment
                {
                    text = "<span style=\"color:" + _csharpColorComments 
                        + ";\">" + HttpUtility.HtmlEncode(text) + "</span>";
                    sbOut.Append(text);
                }
                else
                {
                    words = text.Split('.');

                    for (int i = 0; i  words.Length; i++)
                    {
                        word = words[i];
                        dot = (i  0) ? "." : string.Empty;

                        if (word.StartsWith("\"")) // it's a string
                        {
                            word = "<span style=\"color:" + _csharpColorStrings 
                                + ";\">" + dot + HttpUtility.HtmlEncode(word) + "</span>";
                        }
                        else if (htKeywords[word] != null) // it's C# a keyword
                        {
                            word = "<span style=\"color:" + _csharpColorKeywords 
                                + ";\">" + dot + word + "</span>";
                        }
                        else if (word.IndexOf("//") != -1  word.EndsWith("\n")) // it's a comment
                        {
                            word = "<span style=\"color:" + _csharpColorComments 
                                + ";\">" + dot + HttpUtility.HtmlEncode(word) + "</span>";
                        }
                        else // unrecognized word
                        {
                            word = dot + HttpUtility.HtmlEncode(word);
                        }

                        sbOut.Append(word);
                    }
                }
            }

            return sbOut.ToString();

        }

        private static Hashtable GetCSharpKeywords()
        {
            string[] cskeywords = 
            {
                "abstract","event","new","struct","as","explicit","null","switch","base","extern","object","this","get",
                "bool","false","operator","throw","break","finally","out","true","byte","fixed","override","try","set",
                "case","float","params","typeof","catch","for","private","uint","char","foreach","protected","ulong",
                "checked","goto","public","unchecked","class","if","readonly","unsafe","const","implicit","ref","ushort",
                "continue","in","return","using","decimal","int","sbyte","virtual","default","interface","sealed","volatile",
                "delegate","internal","short","void","do","is","sizeof","while","double","lock","stackalloc","else",
                "long","static","enum","namespace","string"};

            Hashtable htKeywords = new Hashtable();

            foreach (string keyword in cskeywords)
            {
                htKeywords.Add(keyword, keyword);
            }

            return htKeywords;
        }

        public void SetCodeLanguage(string filename)
        {
            this.LoadFileExenstions();
            string extension = this.GetExtension(filename);
            string codelanguage = Convert.ToString(_htExtensions[extension]);

            if ( codelanguage == "csharp")
            {
                this.CodeLanguage = CodeLanguages.CSharp ;
            }
            else if ( codelanguage == "html" )
            {
                this.CodeLanguage = CodeLanguages.Html;
            }
        }
    }
}