/*************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. This code is licensed under the Visual Studio SDK license terms. THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. ***************************************************************************/ using System; using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Text; namespace Microsoft.VisualStudio.Project { /// /// Replacement type /// public enum TokenReplaceType { ReplaceString, ReplaceNumber, ReplaceCode } /// /// Contain a number of functions that handle token replacement /// [CLSCompliant(false)] public class TokenProcessor { #region fields // Internal fields private ArrayList tokenlist; #endregion #region Initialization /// /// Constructor /// public TokenProcessor() { tokenlist = new ArrayList(); } /// /// Reset list of TokenReplacer entries /// public virtual void Reset() { tokenlist.Clear(); } /// /// Add a replacement type entry /// /// token to replace /// replacement string public virtual void AddReplace(string token, string replacement) { tokenlist.Add(new ReplacePairToken(token, replacement)); } /// /// Add replace between entry /// /// Start token /// End token [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "tokenid")] public virtual void AddReplaceBetween(string tokenid, string tokenStart, string tokenEnd, string replacement) { tokenlist.Add(new ReplaceBetweenPairToken(tokenid, tokenStart, tokenEnd, replacement)); } /// /// Add a deletion entry /// /// Token to delete public virtual void AddDelete(string tokenToDelete) { tokenlist.Add(new DeleteToken(tokenToDelete)); } #endregion #region TokenProcessing /// /// For all known token, replace token with correct value /// /// File of the source file /// File of the destination file [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Untoken")] public virtual void UntokenFile(string source, string destination) { if(string.IsNullOrEmpty(source)) throw new ArgumentNullException("source"); if(string.IsNullOrEmpty(destination)) throw new ArgumentNullException("destination"); // Make sure that the destination folder exists. string destinationFolder = Path.GetDirectoryName(destination); if(!Directory.Exists(destinationFolder)) { Directory.CreateDirectory(destinationFolder); } //Open the file. Check to see if the File is binary or text. // NOTE: This is not correct because GetBinaryType will return true // only if the file is executable, not if it is a dll, a library or // any other type of binary file. uint binaryType; if(!NativeMethods.GetBinaryType(source, out binaryType)) { Encoding encoding = Encoding.Default; string buffer = null; // Create the reader to get the text. Note that we will default to ASCII as // encoding if the file does not contains a different signature. using(StreamReader reader = new StreamReader(source, Encoding.ASCII, true)) { // Get the content of the file. buffer = reader.ReadToEnd(); // Detect the encoding of the source file. Note that we // can get the encoding only after a read operation is // performed on the file. encoding = reader.CurrentEncoding; } foreach(object pair in tokenlist) { if(pair is DeleteToken) DeleteTokens(ref buffer, (DeleteToken)pair); if(pair is ReplaceBetweenPairToken) ReplaceBetweenTokens(ref buffer, (ReplaceBetweenPairToken)pair); if(pair is ReplacePairToken) ReplaceTokens(ref buffer, (ReplacePairToken)pair); } File.WriteAllText(destination, buffer, encoding); } else File.Copy(source, destination); } /// /// Replaces the tokens in a buffer with the replacement string /// /// Buffer to update /// replacement data public virtual void ReplaceTokens(ref string buffer, ReplacePairToken tokenToReplace) { if (tokenToReplace == null) { throw new ArgumentNullException("tokenToReplace"); } if (buffer == null) { throw new ArgumentNullException("buffer"); } buffer = buffer.Replace(tokenToReplace.Token, tokenToReplace.Replacement); } /// /// Deletes the token from the buffer /// /// Buffer to update /// token to delete public virtual void DeleteTokens(ref string buffer, DeleteToken tokenToDelete) { if (tokenToDelete == null) { throw new ArgumentNullException("tokenToDelete"); } if (buffer == null) { throw new ArgumentNullException("buffer"); } buffer = buffer.Replace(tokenToDelete.StringToDelete, string.Empty); } /// /// Replaces the token from the buffer between the provided tokens /// /// Buffer to update /// replacement token public virtual void ReplaceBetweenTokens(ref string buffer, ReplaceBetweenPairToken rpBetweenToken) { if (rpBetweenToken == null) { throw new ArgumentNullException("rpBetweenToken"); } if (buffer == null) { throw new ArgumentNullException("buffer"); } string regularExp = rpBetweenToken.TokenStart + "[^" + rpBetweenToken.TokenIdentifier + "]*" + rpBetweenToken.TokenEnd; buffer = System.Text.RegularExpressions.Regex.Replace(buffer, regularExp, rpBetweenToken.TokenReplacement); } #endregion #region Guid generators /// /// Generates a string representation of a guid with the following format: /// 0x01020304, 0x0506, 0x0708, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 /// /// Guid to be generated /// The guid as string [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] public string GuidToForm1(Guid value) { byte[] GuidBytes = value.ToByteArray(); StringBuilder ResultingStr = new StringBuilder(80); // First 4 bytes int i = 0; int Number = 0; for(i = 0; i < 4; ++i) { int CurrentByte = GuidBytes[i]; Number += CurrentByte << (8 * i); } UInt32 FourBytes = (UInt32)Number; ResultingStr.AppendFormat(CultureInfo.InvariantCulture, "0x{0}", FourBytes.ToString("X", CultureInfo.InvariantCulture)); // 2 chunks of 2 bytes for(int j = 0; j < 2; ++j) { Number = 0; for(int k = 0; k < 2; ++k) { int CurrentByte = GuidBytes[i++]; Number += CurrentByte << (8 * k); } UInt16 TwoBytes = (UInt16)Number; ResultingStr.AppendFormat(CultureInfo.InvariantCulture, ", 0x{0}", TwoBytes.ToString("X", CultureInfo.InvariantCulture)); } // 8 chunks of 1 bytes for(int j = 0; j < 8; ++j) { ResultingStr.AppendFormat(CultureInfo.InvariantCulture, ", 0x{0}", GuidBytes[i++].ToString("X", CultureInfo.InvariantCulture)); } return ResultingStr.ToString(); } #endregion #region Helper Methods /// /// This function will accept a subset of the characters that can create an /// identifier name: there are other unicode char that can be inside the name, but /// this function will not allow. By now it can work this way, but when and if the /// VSIP package will handle also languages different from english, this function /// must be changed. /// /// Character to validate /// true if successful false otherwise [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c")] protected static bool IsValidIdentifierChar(char c) { if((c >= 'a') && (c <= 'z')) { return true; } if((c >= 'A') && (c <= 'Z')) { return true; } if(c == '_') { return true; } if((c >= '0') && (c <= '9')) { return true; } return false; } /// /// Verifies if the start character is valid /// /// Start character /// true if successful false otherwise [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c")] protected static bool IsValidIdentifierStartChar(char c) { if(!IsValidIdentifierChar(c)) { return false; } if((c >= '0') && (c <= '9')) { return false; } return true; } /// /// The goal here is to reduce the risk of name conflict between 2 classes /// added in different directories. This code does NOT garanty uniqueness. /// To garanty uniqueness, you should change this function to work with /// the language service to verify that the namespace+class generated does /// not conflict. /// /// Full path to the new file /// Namespace to use for the new file [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] public string GetFileNamespace(string fileFullPath, ProjectNode node) { if (node == null) { throw new ArgumentNullException("node"); } // Get base namespace from the project string namespce = node.GetProjectProperty("RootNamespace"); if(String.IsNullOrEmpty(namespce)) namespce = Path.GetFileNameWithoutExtension(fileFullPath); ; // If the item is added to a subfolder, the name space should reflect this. // This is done so that class names from 2 files with the same name but different // directories don't conflict. string relativePath = Path.GetDirectoryName(fileFullPath); string projectPath = Path.GetDirectoryName(node.GetMkDocument()); // Our project system only support adding files that are sibling of the project file or that are in subdirectories. if(String.Compare(projectPath, 0, relativePath, 0, projectPath.Length, true, CultureInfo.CurrentCulture) == 0) { relativePath = relativePath.Substring(projectPath.Length); } else { Debug.Fail("Adding an item to the project that is NOT under the project folder."); // We are going to use the full file path for generating the namespace } // Get the list of parts int index = 0; string[] pathParts; pathParts = relativePath.Split(Path.DirectorySeparatorChar); // Use a string builder with default size being the expected size StringBuilder result = new StringBuilder(namespce, namespce.Length + relativePath.Length + 1); // For each path part while(index < pathParts.Length) { string part = pathParts[index]; ++index; // This could happen if the path had leading/trailing slash, we want to ignore empty pieces if(String.IsNullOrEmpty(part)) continue; // If we reach here, we will be adding something, so add a namespace separator '.' result.Append('.'); // Make sure it starts with a letter if(!char.IsLetter(part, 0)) result.Append('N'); // Filter invalid namespace characters foreach(char c in part) { if(char.IsLetterOrDigit(c)) result.Append(c); } } return result.ToString(); } #endregion } /// /// Storage classes for replacement tokens /// public class ReplacePairToken { /// /// token string /// private string token; /// /// Replacement string /// private string replacement; /// /// Constructor /// /// replaceable token /// replacement string public ReplacePairToken(string token, string replacement) { this.token = token; this.replacement = replacement; } /// /// Token that needs to be replaced /// public string Token { get { return token; } } /// /// String to replace the token with /// public string Replacement { get { return replacement; } } } /// /// Storage classes for token to be deleted /// public class DeleteToken { /// /// String to delete /// private string token; /// /// Constructor /// /// Deletable token. public DeleteToken(string token) { this.token = token; } /// /// Token marking the end of the block to delete /// public string StringToDelete { get { return token; } } } /// /// Storage classes for string to be deleted between tokens to be deleted /// public class ReplaceBetweenPairToken { /// /// Token start /// private string tokenStart; /// /// End token /// private string tokenEnd; /// /// Replacement string /// private string replacement; /// /// Token identifier string /// private string tokenidentifier; /// /// Constructor /// /// Start token /// End Token /// Replacement string. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "tokenid")] public ReplaceBetweenPairToken(string tokenid, string blockStart, string blockEnd, string replacement) { tokenStart = blockStart; tokenEnd = blockEnd; this.replacement = replacement; tokenidentifier = tokenid; } /// /// Token marking the begining of the block to delete /// public string TokenStart { get { return tokenStart; } } /// /// Token marking the end of the block to delete /// public string TokenEnd { get { return tokenEnd; } } /// /// Token marking the end of the block to delete /// public string TokenReplacement { get { return replacement; } } /// /// Token Identifier /// public string TokenIdentifier { get { return tokenidentifier; } } } }