SnippetComponent.cs :  » Development » Sandcastle » Microsoft » Ddue » Tools » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Development » Sandcastle 
Sandcastle » Microsoft » Ddue » Tools » SnippetComponent.cs
// Copyright  Microsoft Corporation.
// This source file is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.

// <summary>Contains code to insert snippets directly from the source files without using any 
// intermediate XML files.
// </summary>
namespace Microsoft.Ddue.Tools{
    using System;
    using System.Configuration;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Xml;
    using System.Xml.XPath;
    using System.Globalization;
    using System.Diagnostics;

    /// <summary>
    /// SnippetComponent class to replace the snippet code references.
    /// </summary>
    public class SnippetComponent : BuildComponent
    {
        #region Private members
        /// <summary>
        /// Regex to validate the snippet references.
        /// </summary>
        private static Regex validSnippetReference = new Regex(
            @"^[^#\a\b\f\n\r\t\v]+#(\w+,)*\w+$",
            RegexOptions.Compiled);

        /// <summary>
        /// Dictionary to map language folder names to language id.
        /// </summary>
        private static Dictionary<string, string> languageMap = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);

        /// <summary>
        /// List that controls the order in which languages snippets are displayed.
        /// </summary>
        private static List<string> languageList = new List<string>();

        /// <summary>
        /// Dictionary consisting of example name as key and example path as value.
        /// </summary>
        private Dictionary<string, string> exampleIndex = new Dictionary<string, string>();

        /// <summary>
        /// Dictionary consisting of exampleName\unitName as key with a null value.
        /// </summary>
        private Dictionary<string, string> approvedSnippetIndex = new Dictionary<string,string>();

        /// <summary>
        /// Dictionary containing the example name as key and list of rejected language snippets as values.
        /// </summary>
        private Dictionary<string, List<string>> rejectedSnippetIndex = new Dictionary<string, List<string>>();

        /// <summary>
        /// List of unit folder names to exclude from sample parsing.
        /// </summary>
        private Dictionary<string, Object> excludedUnits = new Dictionary<string, Object>();

        /// <summary>
        /// Dictionary consisting of exampleName\unitName as key with a null value.
        /// </summary>
        private SnippetCache snippetCache = null;

        /// <summary>
        /// XPathExpression to look for snippet references in the topics.
        /// </summary>
        private XPathExpression selector;

        /// <summary>
        /// XmlNamespaceManager to set the context.
        /// </summary>
        private XmlNamespaceManager context = new CustomContext();

        /// <summary>
        /// List of languages.
        /// </summary>
        private List<Language> languages = new List<Language>();

        /// <summary>
        /// snippet store.
        /// </summary>
        private Dictionary<SnippetIdentifier, List<Snippet>> snippets = new Dictionary<SnippetIdentifier, List<Snippet>>();
        #endregion

        #region Constructor

        /// <summary>
        /// Constructor for SnippetComponent class.
        /// </summary>
        /// <param name="assembler">An instance of Build Assembler</param>
        /// <param name="configuration">configuration to be parsed for information related to snippets</param>
        public SnippetComponent(BuildAssembler assembler, XPathNavigator configuration)
            : base(assembler, configuration)
        {
            Debug.Assert(assembler != null);
            Debug.Assert(configuration != null);

            // Get the parsnip examples location.
            XPathNodeIterator examplesNode = configuration.Select("examples/example");

            if (examplesNode.Count == 0)
                WriteMessage(MessageLevel.Error, "Each snippet component element must have a child element named 'examples' containing an element named 'example' with an attribute named 'directory', whose value is a path to the directory containing examples.");

            foreach (XPathNavigator exampleNode in examplesNode)
            {
                string rootDirectory = exampleNode.GetAttribute("directory", string.Empty);

                if (string.IsNullOrEmpty(rootDirectory))
                    WriteMessage(MessageLevel.Error, "Each examples element must have a directory attribute specifying a directory containing parsnip samples.");

                rootDirectory = Environment.ExpandEnvironmentVariables(rootDirectory);
                if (!Directory.Exists(rootDirectory))
                    WriteMessage(MessageLevel.Error, String.Format("The examples/@directory attribute specified a directory that doesn't exist: '{0}'", rootDirectory));

                // create a dictionary that maps the example names to the example path under the root directory
                this.loadExamples(rootDirectory);
            }

            // Get the approved log files location.
            XPathNodeIterator approvedSnippetsNode = configuration.Select("approvalLogs/approvalLog");

            if (approvedSnippetsNode.Count == 0)
                WriteMessage(MessageLevel.Warn, "The config did not have an 'approvalLogs' node to specify a snippet approval logs.");
            
            foreach (XPathNavigator node in approvedSnippetsNode)
            {
                string approvalLogFile = node.GetAttribute("file", string.Empty);

                if (string.IsNullOrEmpty(approvalLogFile))
                    WriteMessage(MessageLevel.Error, "The approvalLog node must have a 'file' attribute specifying the path to a snippet approval log.");

                approvalLogFile = Environment.ExpandEnvironmentVariables(approvalLogFile);
                if (!File.Exists(approvalLogFile))
                    WriteMessage(MessageLevel.Error, String.Format("The approvalLog/@file attribute specified a file that doesn't exist: '{0}'", approvalLogFile));

                // load the approval log into the approvedSnippetIndex dictionary
                this.parseApprovalLogFiles(approvalLogFile);
            }

            // Get the names of the unit directories in the sample tree to exclude from parsing
            //     <excludedUnits><unitFolder name="CPP_OLD" /></excludedUnits>
            XPathNodeIterator excludedUnitNodes = configuration.Select("excludedUnits/unitFolder");
            foreach (XPathNavigator unitFolder in excludedUnitNodes)
            {
                string folderName = unitFolder.GetAttribute("name", string.Empty);

                if (string.IsNullOrEmpty(folderName))
                    WriteMessage(MessageLevel.Error, "Each excludedUnits/unitFolder node must have a 'name' attribute specifying the name of a folder name to exclude.");

                folderName = Environment.ExpandEnvironmentVariables(folderName);

                // add the folderName to the list of names to be excluded
                this.excludedUnits.Add(folderName.ToLower(),null);
            }

            // Get the languages defined.
            XPathNodeIterator languageNodes = configuration.Select("languages/language");
            foreach (XPathNavigator languageNode in languageNodes)
            {
                // read the @languageId, @unit, and @extension attributes
                string languageId = languageNode.GetAttribute("languageId", string.Empty);
                if (string.IsNullOrEmpty(languageId))
                    WriteMessage(MessageLevel.Error, "Each language node must specify an @languageId attribute.");

                string unit = languageNode.GetAttribute("unit", string.Empty);

                // if both @languageId and @unit are specified, add this language to the language map
                if (!string.IsNullOrEmpty(unit))
                    languageMap.Add(unit.ToLower(), languageId);

                // add languageId to the languageList for purpose of ordering snippets in the output
                if (!languageList.Contains(languageId))
                    languageList.Add(languageId.ToLower());

                string extension = languageNode.GetAttribute("extension", string.Empty);
                if (!string.IsNullOrEmpty(extension))
                {
                    if (!extension.Contains("."))
                    {
                        extension = "." + extension;
                        WriteMessage(MessageLevel.Warn, String.Format("The @extension value must begin with a period. Adding a period to the extension value '{0}' of the {1} language.", extension, languageId));
                    }
                    else
                    {
                        int indexOfPeriod = extension.IndexOf('.');
                        if (indexOfPeriod != 0)
                        {
                            extension = extension.Substring(indexOfPeriod);
                            WriteMessage(MessageLevel.Warn, String.Format("The @extension value must begin with a period. Using the substring beginning with the first period of the specified extension value '{0}' of the {1} language.", extension, languageId));
                        }
                    }
                }

                // read the color nodes, if any, and add them to the list of colorization rules
                List<ColorizationRule> rules = new List<ColorizationRule>();

                XPathNodeIterator colorNodes = languageNode.Select("color");
                foreach (XPathNavigator colorNode in colorNodes)
                {
                    string pattern = colorNode.GetAttribute("pattern", String.Empty);
                    string region = colorNode.GetAttribute("region", String.Empty);
                    string name = colorNode.GetAttribute("class", String.Empty);
                    if (String.IsNullOrEmpty(region))
                    {
                        rules.Add(new ColorizationRule(pattern, name));
                    }
                    else
                    {
                        rules.Add(new ColorizationRule(pattern, region, name));
                    }
                }

                this.languages.Add(new Language(languageId, extension, rules));
                WriteMessage(MessageLevel.Info, String.Format("Loaded {0} colorization rules for the language '{1}', extension '{2}.", rules.Count, languageId, extension));
            }

            this.context.AddNamespace("ddue", "http://ddue.schemas.microsoft.com/authoring/2003/5");
            this.selector = XPathExpression.Compile("//ddue:codeReference");
            this.selector.SetContext(this.context);

            // create the snippet cache
            snippetCache = new SnippetCache(100, approvedSnippetIndex, languageMap, languages, excludedUnits);
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Apply method to perform the actual work of the component.
        /// </summary>
        /// <param name="document">document to be parsed for snippet references</param>
        /// <param name="key">Id of a topic</param>
        public override void Apply(XmlDocument document, string key)
        {
            // clear out the snippets dictionary of any snippets from the previous document
            snippets.Clear();

            XPathNodeIterator nodesIterator = document.CreateNavigator().Select(this.selector);
            XPathNavigator[] nodes = BuildComponentUtilities.ConvertNodeIteratorToArray(nodesIterator);
            foreach (XPathNavigator node in nodes)
            {
                // get the snippet reference, which can contain one or more snippet ids
                string reference = node.Value;

                // check for validity of reference
                if (!validSnippetReference.IsMatch(reference))
                {
                    WriteMessage(MessageLevel.Warn, "Skipping invalid snippet reference: " + reference);
                    continue;
                }

                // get the identifiers from the codeReference
                SnippetIdentifier[] identifiers = SnippetIdentifier.ParseReference(reference);

                // load the language-specific snippets for each of the specified identifiers
                foreach (SnippetIdentifier identifier in identifiers)
                {
                    if (snippets.ContainsKey(identifier))
                        continue;

                    // look up the snippets example path
                    string examplePath = string.Empty;
                    if (!this.exampleIndex.TryGetValue(identifier.Example, out examplePath))
                    {
                        WriteMessage(MessageLevel.Warn, String.Format("Snippet with identifier '{0}' was not found. The '{1}' example was not found in the examples directory.", identifier.ToString(), identifier.Example));
                        continue;
                    }

                    // get the snippet from the snippet cache 
                    List<Snippet> snippetList = snippetCache.GetContent(examplePath, identifier);
                    if (snippetList != null)
                    {
                        snippets.Add(identifier, snippetList);
                    }
                    else 
                    {
                        // if no approval log was specified in the config, all snippets are treated as approved by default
                        // so show an warning message that the snippet was not found
                        if (approvedSnippetIndex.Count == 0)
                            WriteMessage(MessageLevel.Warn, string.Format("No Snippet with identifier '{0}' was found.", identifier.ToString()));
                        else
                        {
                            // show a warning message: either snippet not found, or snippet not approved.
                            bool isApproved = false;

                            foreach (string snippetIndex in this.approvedSnippetIndex.Keys)
                            {
                                string[] splitSnippet = snippetIndex.Split('\\');
                                if (splitSnippet[0] == identifier.Example)
                                {
                                    isApproved = true;
                                    break;
                                }
                            }

                            // check whether snippets are present in parsnip approval logs and throw warnings accordingly.
                            if (!isApproved || !rejectedSnippetIndex.ContainsKey(identifier.Example))
                                WriteMessage(MessageLevel.Warn, string.Format("The snippet with identifier '{0}' was omitted because it is not present in parsnip approval logs.", identifier.ToString()));
                            else
                                WriteMessage(MessageLevel.Warn, string.Format("No Snippet with identifier '{0}' was found.", identifier.ToString()));
                        }

                        continue;
                    }

                    // write warning messages for any rejected units for this example
                    List<string> rejectedUnits;
                    if (rejectedSnippetIndex.TryGetValue(identifier.Example, out rejectedUnits))
                    {
                        foreach (string rejectedUnit in rejectedUnits)
                            WriteMessage(MessageLevel.Warn, string.Format("The '{0}' snippet with identifier '{1}' was omitted because the {2}\\{0} unit did not pass Parsnip testing.", rejectedUnit, identifier.ToString(), identifier.Example));
                    }
                }

                if (identifiers.Length == 1)
                {
                    // one snippet referenced
                    SnippetIdentifier identifier = identifiers[0];

                    if (snippets.ContainsKey(identifier))
                    {
                        writeSnippetContent(node, identifier, snippets[identifier]);
                    }
                }
                else
                {
                    // handle case where codeReference contains multiple identifiers
                    // Each language's set of snippets from multiple identifiers are displayed in a single block; 

                    // create dictionary that maps each language to its set of snippets
                    Dictionary<string, List<Snippet>> map = new Dictionary<string, List<Snippet>>();
                    foreach (SnippetIdentifier identifier in identifiers)
                    {
                        List<Snippet> values;
                        if (snippets.TryGetValue(identifier, out values))
                        {
                            foreach (Snippet value in values)
                            {
                                List<Snippet> pieces;
                                if (!map.TryGetValue(value.Language.LanguageId, out pieces))
                                {
                                    pieces = new List<Snippet>();
                                    map.Add(value.Language.LanguageId, pieces);
                                }
                                pieces.Add(value);
                            }
                        }
                    }

                    // now write the collection of snippet pieces to the document
                    XmlWriter writer = node.InsertAfter();
                    writer.WriteStartElement("snippets");
                    writer.WriteAttributeString("reference", reference);

                    // first write the snippets in the order their language shows up in the language map (if any)
                    foreach (string devlang in languageList)
                    {
                        foreach (KeyValuePair<string, List<Snippet>> entry in map)
                        {
                            if (!(devlang == entry.Key.ToLower()))
                                continue;
                            writer.WriteStartElement("snippet");
                            writer.WriteAttributeString("language", entry.Key);
                            writer.WriteString("\n");

                            // write the set of snippets for this language
                            List<Snippet> values = entry.Value;
                            for (int i = 0; i < values.Count; i++)
                            {
                                if (i > 0)
                                    writer.WriteString("\n...\n\n\n");
                                // write the colorized or plaintext snippet text
                                WriteSnippetText(values[i], writer);
                            }
                            
                            writer.WriteEndElement();
                        }
                    }

                    // now write any snippets whose language isn't in the language map
                    foreach (KeyValuePair<string, List<Snippet>> entry in map)
                    {
                        if (languageList.Contains(entry.Key.ToLower()))
                            continue;
                        writer.WriteStartElement("snippet");
                        writer.WriteAttributeString("language", entry.Key);
                        writer.WriteString("\n");

                        // write the set of snippets for this language
                        List<Snippet> values = entry.Value;
                        for (int i = 0; i < values.Count; i++)
                        {
                            if (i > 0)
                                writer.WriteString("\n...\n\n\n");
                            // write the colorized or plaintext snippet text
                            WriteSnippetText(values[i], writer);
                        }
                        
                        writer.WriteEndElement();
                    }
                    
                    writer.WriteEndElement();
                    writer.Close();
                }
                node.DeleteSelf();
            }
        }
        #endregion

        #region Private methods
        /// <summary>
        /// Index the example names to paths.
        /// </summary>
        /// <param name="rootDirectory">root directory location of parsnip samples</param>
        private void loadExamples(string rootDirectory)
        {
            try
            {
                DirectoryInfo root = new DirectoryInfo(rootDirectory);
                DirectoryInfo[] areaDirectories = root.GetDirectories();

                foreach (DirectoryInfo area in areaDirectories)
                {
                    DirectoryInfo[] exampleDirectories = area.GetDirectories();

                    foreach (DirectoryInfo example in exampleDirectories)
                    {
                        string path;
                        if (this.exampleIndex.TryGetValue(example.Name.ToLower(CultureInfo.InvariantCulture), out path))
                            WriteMessage(MessageLevel.Warn, string.Format("The example '{0}' under folder '{1}' already exists under '{2}'", example.Name, example.FullName, path));
                        
                        this.exampleIndex[example.Name.ToLower(CultureInfo.InvariantCulture)] = example.FullName;
                    }
                }
            }
            catch (Exception e)
            {
                WriteMessage(MessageLevel.Error, string.Format(System.Threading.Thread.CurrentThread.CurrentCulture, "The loading of examples failed:{0}", e.Message));
                throw;
            }
        }

        /// <summary>
        /// Index the approved snippets.
        /// </summary>
        /// <param name="file">approved snippets log file</param>
        private void parseApprovalLogFiles(string file)
        {
            string sampleName = string.Empty;
            string unitName = string.Empty;
            List<string> rejectedUnits = null;
           
            XmlReader reader = XmlReader.Create(file);
            try
            {
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        if (reader.Name == "Sample")
                        {
                           sampleName = reader.GetAttribute("name").ToLower(CultureInfo.InvariantCulture);
                           //create a new rejectedUnits list for this sample
                           rejectedUnits = null;
                        }

                        if (reader.Name == "Unit")
                        {
                            unitName = reader.GetAttribute("name").ToLower(CultureInfo.InvariantCulture);

                            bool include = Convert.ToBoolean(reader.GetAttribute("include"));

                            if (include)
                            {
                               if (this.approvedSnippetIndex.ContainsKey(Path.Combine(sampleName, unitName)))
                                    WriteMessage(MessageLevel.Warn, string.Format("Sample '{0}' already exists in the approval log files.", sampleName));
                                this.approvedSnippetIndex[Path.Combine(sampleName, unitName)] = null;
                            }
                            else
                            {
                                if (rejectedUnits == null)
                                {
                                    rejectedUnits = new List<string>();
                                    rejectedSnippetIndex[sampleName] = rejectedUnits;
                                }
                                rejectedUnits.Add(unitName);
                            }
                        }
                    }
                }
            }
            catch (XmlException e)
            {
                WriteMessage(MessageLevel.Error, String.Format("The specified approval log file is not well-formed. The error message is: {0}", e.Message));
            }
            finally
            {
                reader.Close();
            }
        }

        /// <summary>
        /// Write the snippet content to output files.
        /// </summary>
        /// <param name="node">code reference node</param>
        /// <param name="identifier">List of snippets</param>
        private void writeSnippetContent(XPathNavigator node, SnippetIdentifier identifier, List<Snippet> snippetList)
        {
            if (snippetList == null || snippetList.Count == 0)
            {
                WriteMessage(MessageLevel.Warn, "Empty snippet list past for node " + node.Name);
                return;
            }

            XmlWriter writer = node.InsertAfter();
            writer.WriteStartElement("snippets");
            writer.WriteAttributeString("reference", node.Value);

            // first write the snippets in the order their language shows up in the language map (if any)
            foreach (string devlang in languageList)
            {
                foreach (Snippet snippet in snippetList)
                {
                    if (!(devlang == snippet.Language.LanguageId.ToLower()))
                        continue;
                    writer.WriteStartElement("snippet");
                    writer.WriteAttributeString("language", snippet.Language.LanguageId);
                    writer.WriteString("\n");
                    // write the colorized or plaintext snippet text
                    WriteSnippetText(snippet, writer);
                    writer.WriteEndElement();
                }
            }

            // now write any snippets whose language isn't in the language map
            foreach (Snippet snippet in snippetList)
            {
                if (languageList.Contains(snippet.Language.LanguageId.ToLower()))
                    continue;
                writer.WriteStartElement("snippet");
                writer.WriteAttributeString("language", snippet.Language.LanguageId);
                writer.WriteString("\n");
                // write the colorized or plaintext snippet text
                WriteSnippetText(snippet, writer);
                writer.WriteEndElement();
            }
            
            writer.WriteEndElement();
            writer.Close();
        }

        private void WriteSnippetText(Snippet snippet, XmlWriter writer)
        {
            // if colorization rules are defined, then colorize the snippet.
            if (snippet.Language.ColorizationRules != null)
            {
                writeColorizedSnippet(colorizeSnippet(snippet.Content, snippet.Language.ColorizationRules), writer);
            }
            else
            {
                writer.WriteString(snippet.Content);
            }
        }

        private static ICollection<Region> colorizeSnippet(string text, List<ColorizationRule> rules)
        {
            // Console.WriteLine("colorizing: '{0}'", text);
            // create a linked list consiting entirely of one uncolored region
            LinkedList<Region> regions = new LinkedList<Region>();
            regions.AddFirst(new Region(text));

            // loop over colorization rules
            foreach (ColorizationRule rule in rules)
            {
                // loop over regions
                LinkedListNode<Region> node = regions.First;
                while (node != null)
                {
                    // only try to colorize uncolored regions
                    if (node.Value.ClassName != null)
                    {
                        node = node.Next;
                        continue;
                    }

                    // find matches in the region
                    string regionText = node.Value.Text;
                    Capture[] matches = rule.Apply(regionText);

                    // if no matches were found, continue to the next region
                    if (matches.Length == 0)
                    {
                        node = node.Next;
                        continue;
                    }

                    // we found matches; break the region into colored and uncolered subregions
                    // index is where we are looking from; index-1 is the end of the last match
                    int index = 0;

                    LinkedListNode<Region> referenceNode = node;

                    foreach (Capture match in matches)
                    {
                        // create a leading uncolored region 
                        if (match.Index > index)
                        {
                            //Console.WriteLine("uncolored: {0} '{1}' -> {2} '{3}'", index, regionText[index], match.Index - 1, regionText[match.Index - 1]); 
                            Region uncoloredRegion = new Region(regionText.Substring(index, match.Index - index));
                            referenceNode = regions.AddAfter(referenceNode, uncoloredRegion);
                        }

                        // create a colored region
                        // Console.WriteLine("name = {0}", rule.ClassName);
                        //Console.WriteLine("colored: {0} '{1}' -> {2} '{3}'", match.Index, regionText[match.Index], match.Index + match.Length - 1, regionText[match.Index + match.Length - 1]);
                        Region coloredRegion = new Region(rule.ClassName, regionText.Substring(match.Index, match.Length));
                        referenceNode = regions.AddAfter(referenceNode, coloredRegion);

                        index = match.Index + match.Length;
                    }

                    // create a trailing uncolored region
                    if (index < regionText.Length)
                    {
                        Region uncoloredRegion = new Region(regionText.Substring(index));
                        referenceNode = regions.AddAfter(referenceNode, uncoloredRegion);
                    }

                    // remove the original node
                    regions.Remove(node);
                    node = referenceNode.Next;
                }
            }
            return (regions);
        }

        private static void writeColorizedSnippet(ICollection<Region> regions, XmlWriter writer)
        {
            foreach (Region region in regions)
            {
                // Console.WriteLine("writing {0}", region.ClassName);
                if (region.ClassName == null)
                {
                    writer.WriteString(region.Text);
                }
                else
                {
                    writer.WriteStartElement("span");
                    writer.WriteAttributeString("class", region.ClassName);
                    writer.WriteString(region.Text);
                    writer.WriteEndElement();
                }
            }
        }
        #endregion
    }

    /// <summary>
    /// Language class.
    /// </summary>
    internal class Language
    {
        #region Private members
        /// <summary>
        /// The id of the programming language.
        /// </summary>
        private string languageId;

        /// <summary>
        /// Language file extension.
        /// </summary>
        private string extension;

        /// <summary>
        /// List of colorization rules.
        /// </summary>
        private List<ColorizationRule> colorizationRules;
        #endregion

        #region Constructor
        /// <summary>
        /// Language Constructor
        /// </summary>
        /// <param name="languageId">language id</param>
        /// <param name="extension">language file extension</param>
        /// <param name="rules">colorization rules</param>
        public Language(string languageId, string extension, List<ColorizationRule> rules)
        {
            this.languageId = languageId;
            this.extension = extension;
            this.colorizationRules = rules;
        }
        #endregion

        #region Public properties
        /// <summary>
        /// Gets the languageId.
        /// </summary>
        public string LanguageId
        {
            get
            {
                return this.languageId;
            }
        }

        /// <summary>
        /// Gets the file extension
        /// </summary>
        public string Extension
        {
            get
            {
                return this.extension;
            }
        }

        /// <summary>
        /// Gets the colorization rules
        /// </summary>
        public List<ColorizationRule> ColorizationRules
        {
            get
            {
                return this.colorizationRules;
            }
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Check if the language is defined.
        /// </summary>
        /// <param name="languageId">language id</param>
        /// <param name="extension">file extension</param>
        /// <returns>boolean indicating if a language is defined</returns>
        public bool IsMatch(string languageId, string extension)
        {
            if (this.languageId == languageId)
            {
                if (this.extension == extension)
                {
                    return true;
                }
                else if (this.extension == "*")
                {
                    return true;
                }
            }
            else if (this.languageId == "*")
            {
                if (this.extension == extension)
                {
                    return true;
                }

                if (this.extension == "*")
                {
                    return true;
                }
            }

            return false;
        }
        #endregion
    }

    /// <summary>
    /// Snippet class.
    /// </summary>
    internal class Snippet
    {
        #region Private Members
        /// <summary>
        /// snippet content.
        /// </summary>
        private string content;

        /// <summary>
        /// snippet language
        /// </summary>
        private Language language;
        #endregion

        #region Constructor
        /// <summary>
        /// Constructor for Snippet class.
        /// </summary>
        /// <param name="content">snippet content</param>
        /// <param name="language">snippet language</param>
        public Snippet(string content, Language language)
        {
            this.content = content;
            this.language = language;
        }
        #endregion

        #region Public properties
        /// <summary>
        /// Gets the snippet content.
        /// </summary>
        public string Content
        {
            get
            {
                return this.content;
            }
        }

        /// <summary>
        /// Gets the snippet language.
        /// </summary>
        public Language Language
        {
            get 
            {
                return this.language;
            }
        }
        #endregion
    }

    internal class SnippetCache
    {
        private int _cacheSize = 100;

        private LinkedList<String> lruLinkedList;

        private Dictionary<string, IndexedExample> cache;

        private Dictionary<string, string> _approvalIndex;
        private Dictionary<string, string> _languageMap;
        private List<Language> _languages;
        private Dictionary<string, Object> _excludedUnits;

        public SnippetCache(int cacheSize, Dictionary<string, string> approvalIndex, Dictionary<string, string> languageMap, List<Language> languages, Dictionary<string, Object> excludedUnits)
    {
      _cacheSize = cacheSize;
            _approvalIndex = approvalIndex;
            _languageMap = languageMap;
            _languages = languages;
            _excludedUnits = excludedUnits;

            cache = new Dictionary<string, IndexedExample>(_cacheSize);

      lruLinkedList = new LinkedList<string>();
    }

        public List<Snippet> GetContent(string examplePath, SnippetIdentifier snippetId)
        {
            
            // get the example containing the identifier
            IndexedExample exampleIndex = GetCachedExample(examplePath);
            if (exampleIndex == null) 
                return (null);

            // 
            return exampleIndex.GetContent(snippetId);
        }
               
        private IndexedExample GetCachedExample(string examplePath)
        {
            IndexedExample exampleIndex;
            if (cache.TryGetValue(examplePath, out exampleIndex))
            {
                // move the file from its current position to the head of the lru linked list
                lruLinkedList.Remove(exampleIndex.ListNode);
                lruLinkedList.AddFirst(exampleIndex.ListNode);
            }
            else
            {
                // not in the cache, so load and index a new example
                exampleIndex = new IndexedExample(examplePath, _approvalIndex, _languageMap, _languages, _excludedUnits);
                if (cache.Count >= _cacheSize)
                {
                    // the cache is full
                    // the last node in the linked list has the path of the next file to remove from the cache
                    if (lruLinkedList.Last != null)
                    {
                        cache.Remove(lruLinkedList.Last.Value);
                        lruLinkedList.RemoveLast();
                    }
                }
                // add the new file to the cache and to the head of the lru linked list
                cache.Add(examplePath, exampleIndex);
                exampleIndex.ListNode = lruLinkedList.AddFirst(examplePath);
            }
            return (exampleIndex);
        }


    }

    internal class IndexedExample
    {
        /// <summary>
        /// snippet store.
        /// </summary>
        private Dictionary<SnippetIdentifier, List<Snippet>> exampleSnippets = new Dictionary<SnippetIdentifier, List<Snippet>>();
        private Dictionary<string, string> _approvalIndex;
        private Dictionary<string, string> _languageMap;
        private List<Language> _languages;
        private Dictionary<string, Object> _excludedUnits;

        public IndexedExample(string examplePath, Dictionary<string, string> approvalIndex, Dictionary<string, string> languageMap, List<Language> languages, Dictionary<string, Object> excludedUnits)
        {
            _approvalIndex = approvalIndex;
            _languageMap = languageMap;
            _languages = languages;
            _excludedUnits = excludedUnits;

            // load all the snippets under the specified example path
            this.ParseExample(new DirectoryInfo(examplePath));
        }

        public List<Snippet> GetContent(SnippetIdentifier identifier)
        {
            if (exampleSnippets.ContainsKey(identifier))
                return exampleSnippets[identifier];
            else
                return null;
        }
                
        private LinkedListNode<string> listNode;
        public LinkedListNode<string> ListNode
        {
            get
            {
                return (listNode);
            }
            set
            {
                listNode = value;
            }
        }

        /// <summary>
        /// Check whether the snippet unit is approved
        /// </summary>
        /// <param name="unit">unit directory</param>
        /// <returns>boolean indicating whether the snippet unit is approved</returns>
        private bool isApprovedUnit(DirectoryInfo unit)
        {
            string sampleName = unit.Parent.Name.ToLower(CultureInfo.InvariantCulture);
            string unitName = unit.Name.ToLower(CultureInfo.InvariantCulture);

            // return false if the unit name is in the list of names to exclude
            if (_excludedUnits.ContainsKey(unitName))
                return false;

            // if no approval log is specified, all snippets are approved by default
            if (_approvalIndex.Count == 0)
                return true;

            if (_approvalIndex.ContainsKey(Path.Combine(sampleName, unitName)))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Parse the example directory.
        /// </summary>
        /// <param name="unit">unit directory</param>
        private void ParseExample(DirectoryInfo exampleDirectory)
        {
            // process the approved language-specific unit directories for this example
            DirectoryInfo[] unitDirectories = exampleDirectory.GetDirectories();

            foreach (DirectoryInfo unit in unitDirectories)
            {
                if (this.isApprovedUnit(unit))
                    this.ParseUnit(unit);
            }
        }

        /// <summary>
        /// Parse the unit directory for language files.
        /// </summary>
        /// <param name="unit">unit directory containing a language-specific version of the example</param>
        private void ParseUnit(DirectoryInfo unit)
        {
            // the language is the Unit Directory name, or the language id mapped to that name
            string language = unit.Name;
            if (_languageMap.ContainsKey(language.ToLower()))
                language = _languageMap[language.ToLower()];

            ParseDirectory(unit, language, unit.Parent.Name);
        }

        /// <summary>
        /// Parse an example subdir looking for source files containing snipppets.
        /// </summary>
        /// <param name="directory">The directory to parse</param>
        /// <param name="language">the id of a programming language</param>
        /// <param name="exampleName">the name of the example</param>
        private void ParseDirectory(DirectoryInfo directory, string language, string exampleName)
        {
            // parse the files in this directory
            FileInfo[] files = directory.GetFiles();
            foreach (FileInfo file in files)
                ParseFile(file, language, exampleName);

            // recurse to get files in any subdirectories
            DirectoryInfo[] subdirectories = directory.GetDirectories();
            foreach (DirectoryInfo subdirectory in subdirectories)
                ParseDirectory(subdirectory, language, exampleName);
        }

        /// <summary>
        /// Parse the language files to retrieve the snippet content.
        /// </summary>
        /// <param name="file">snippet file</param>
        /// <param name="language">snippet language</param>
        /// <param name="example">name of the example that contains this file</param>
        private void ParseFile(FileInfo file, string language, string exampleName)
        {
            string snippetLanguage = string.Empty;

            // The snippet language is the name (or id mapping) of the Unit folder
            // unless the file extension is .xaml
            // NOTE: this is just preserving the way ExampleBuilder handled it (which we can change when we're confident there are no unwanted side-effects)
            if (file.Extension.ToLower() == ".xaml")
                snippetLanguage = "XAML";
            else
                snippetLanguage = language;

            // get the text in the file
            StreamReader reader = file.OpenText();
            string text = reader.ReadToEnd();
            reader.Close();

            this.parseSnippetContent(text, snippetLanguage, file.Extension, exampleName);
        }

        /// <summary>
        /// Parse the snippet content.
        /// </summary>
        /// <param name="text">content to be parsed</param>
        /// <param name="language">snippet language</param>
        /// <param name="extension">file extension</param>
        /// <param name="example">snippet example</param>
        private void parseSnippetContent(string text, string language, string extension, string example)
        {
            // parse the text for snippets
            for (Match match = find.Match(text); match.Success; match = find.Match(text, match.Index + 10))
            {
                string snippetIdentifier = match.Groups["id"].Value;
                string snippetContent = match.Groups["tx"].Value;
                snippetContent = clean.Replace(snippetContent, "\n");

                //if necessary, clean one more time to catch snippet comments on consecutive lines
                if (clean.Match(snippetContent).Success)
                {
                    snippetContent = clean.Replace(snippetContent, "\n");
                }

                snippetContent = cleanAtStart.Replace(snippetContent, "");
                snippetContent = cleanAtEnd.Replace(snippetContent, "");

                // get the language/extension from our languages List, which may contain colorization rules for the language
                Language snippetLanguage = new Language(language, extension, null);
                foreach (Language lang in _languages)
                {
                    if (!lang.IsMatch(language, extension))
                        continue;
                    snippetLanguage = lang;
                    break;
                }

                SnippetIdentifier identifier = new SnippetIdentifier(example, snippetIdentifier);
                // BUGBUG: i don't think this ever happens, but if it did we should write an error
                if (!IsLegalXmlText(snippetContent))
                {
                    // WriteMessage(MessageLevel.Warn, String.Format("Snippet '{0}' language '{1}' contains illegal characters.", identifier.ToString(), snippetLanguage.LanguageId));
                    continue;
                }

                snippetContent = StripLeadingSpaces(snippetContent);

                // Add the snippet information to dictionary
                Snippet snippet = new Snippet(snippetContent, snippetLanguage);
                List<Snippet> values;

                if (!this.exampleSnippets.TryGetValue(identifier, out values))
                {
                    values = new List<Snippet>();
                    this.exampleSnippets.Add(identifier, values);
                }
                values.Add(snippet);
            }
        }

        private bool IsLegalXmlText(string text)
        {
            foreach (char c in text)
            {
                if (!IsLegalXmlCharacter(c)) return (false);
            }
            return (true);
        }

        private bool IsLegalXmlCharacter(char c)
        {
            if (c < 0x20)
            {
                return ((c == 0x09) || (c == 0x0A) || (c == 0x0D));
            }
            else
            {
                if (c < 0xD800)
                {
                    return (true);
                }
                else
                {
                    return ((c >= 0xE000) && (c <= 0xFFFD));
                }
            }
        }

        private static string StripLeadingSpaces(string text)
        {

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

            // split the text into lines
            string[] stringSeparators = new string[] { "\r\n" };
            string[] lines = text.Split(stringSeparators, StringSplitOptions.None);

            // no need to do this if there is only one line
            if (lines.Length == 1) return (lines[0]);

            // figure out how many leading spaces to delete
            int spaces = Int32.MaxValue;
            for (int i = 0; i < lines.Length; i++)
            {

                string line = lines[i];

                // skip empty lines
                if (line.Length == 0) continue;

                // determine the number of leading spaces
                int index = 0;
                while (index < line.Length)
                {
                    if (line[index] != ' ') break;
                    index++;
                }

                if (index == line.Length)
                {
                    // lines that are all spaces should just be treated as empty
                    lines[i] = String.Empty;
                }
                else
                {
                    // otherwise, keep track of the minimum number of leading spaces        
                    if (index < spaces) spaces = index;
                }

            }

            // re-form the string with leading spaces deleted
            StringBuilder result = new StringBuilder();
            foreach (string line in lines)
            {
                if (line.Length == 0)
                {
                    result.AppendLine();
                }
                else
                {
                    result.AppendLine(line.Substring(spaces));
                }
            }
            // Console.WriteLine("AFTER:");
            // Console.WriteLine(result.ToString());
            return (result.ToString());

        }    

        /// <summary>
        /// Regex to find the snippet content.
        /// </summary>
        private static Regex find = new Regex(
            @"<snippet(?<id>\w+)>.*\n(?<tx>(.|\n)*?)\n.*</snippet(\k<id>)>",
            RegexOptions.IgnoreCase | RegexOptions.Compiled);

        /// <summary>
        /// Regex to clean the snippet content.
        /// </summary>
        private static Regex clean = new Regex(
            @"\n[^\n]*?<(/?)snippet(\w+)>[^\n]*?\n",
            RegexOptions.IgnoreCase | RegexOptions.Compiled);

        /// <summary>
        /// Regex to clean the start of the snippet.
        /// </summary>
        private static Regex cleanAtStart = new Regex(
            @"^[^\n]*?<(/?)snippet(\w+)>[^\n]*?\n",
            RegexOptions.IgnoreCase | RegexOptions.Compiled);

        /// <summary>
        /// Regex to clean the end of the snippet.
        /// </summary>
        private static Regex cleanAtEnd = new Regex(
            @"\n[^\n]*?<(/?)snippet(\w+)>[^\n]*?$",
            RegexOptions.IgnoreCase | RegexOptions.Compiled);

    }


}

www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.