// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Text; namespace ICSharpCode.Core { /// /// This class parses internal ${xyz} tags of #Develop. /// All environment variables are avaible under the name env.[NAME] /// where [NAME] represents the string under which it is avaiable in /// the environment. /// public static class StringParser { readonly static ConcurrentDictionary prefixedStringTagProviders = InitializePrefixedStringTagProviders(); // not really a stack - we only use Add and GetEnumerator readonly static ConcurrentStack stringTagProviders = new ConcurrentStack(); static ConcurrentDictionary InitializePrefixedStringTagProviders() { var dict = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); // entryAssembly == null might happen in unit test mode Assembly entryAssembly = Assembly.GetEntryAssembly(); if (entryAssembly != null) { string exeName = entryAssembly.Location; dict["exe"] = new PropertyObjectTagProvider(FileVersionInfo.GetVersionInfo(exeName)); } return dict; } /// /// Expands ${xyz} style property values. /// public static string Parse(string input) { return Parse(input, (StringTagPair[])null); } /// /// Parses an array and replaces the elements in the existing array. /// [Obsolete("Call Parse(string) in a loop / consider using LINQ Select instead")] public static void Parse(string[] inputs) { for (int i = 0; i < inputs.Length; ++i) { inputs[i] = Parse(inputs[i]); } } public static void RegisterStringTagProvider(IStringTagProvider tagProvider) { if (tagProvider == null) throw new ArgumentNullException("tagProvider"); stringTagProviders.Push(tagProvider); } public static void RegisterStringTagProvider(string prefix, IStringTagProvider tagProvider) { if (prefix == null) throw new ArgumentNullException("prefix"); if (tagProvider == null) throw new ArgumentNullException("tagProvider"); prefixedStringTagProviders[prefix] = tagProvider; } //readonly static Regex pattern = new Regex(@"\$\{([^\}]*)\}", RegexOptions.Compiled | RegexOptions.CultureInvariant); /// /// Expands ${xyz} style property values. /// public static string Parse(string input, params StringTagPair[] customTags) { if (input == null) return null; int pos = 0; StringBuilder output = null; // don't use StringBuilder if input is a single property do { int oldPos = pos; pos = input.IndexOf("${", pos, StringComparison.Ordinal); if (pos < 0) { if (output == null) { return input; } else { if (oldPos < input.Length) { // normal text after last property output.Append(input, oldPos, input.Length - oldPos); } return output.ToString(); } } if (output == null) { if (pos == 0) output = new StringBuilder(); else output = new StringBuilder(input, 0, pos, pos + 16); } else { if (pos > oldPos) { // normal text between two properties output.Append(input, oldPos, pos - oldPos); } } int end = input.IndexOf('}', pos + 1); if (end < 0) { output.Append("${"); pos += 2; } else { string property = input.Substring(pos + 2, end - pos - 2); string val = GetValue(property, customTags); if (val == null) { output.Append("${"); output.Append(property); output.Append('}'); } else { output.Append(val); } pos = end + 1; } } while (pos < input.Length); return output.ToString(); } /// /// For new code, please use the overload taking StringTagPair[]! /// [Obsolete("Please use the overload taking StringTagPair[]!")] public static string Parse(string input, string[,] customTags) { if (customTags == null) return Parse(input); if (customTags.GetLength(1) != 2) throw new ArgumentException("incorrect dimension"); StringTagPair[] pairs = new StringTagPair[customTags.GetLength(0)]; for (int i = 0; i < pairs.Length; i++) { pairs[i] = new StringTagPair(customTags[i, 0], customTags[i, 1]); } return Parse(input, pairs); } /// /// Evaluates a property using the StringParser. Equivalent to StringParser.Parse("${" + propertyName + "}"); /// public static string GetValue(string propertyName, params StringTagPair[] customTags) { if (propertyName == null) throw new ArgumentNullException("propertyName"); if (customTags != null) { foreach (StringTagPair pair in customTags) { if (propertyName.Equals(pair.Tag, StringComparison.OrdinalIgnoreCase)) { return pair.Value; } } } int k = propertyName.IndexOf(':'); if (k <= 0) { // it's property without prefix if (propertyName.Equals("DATE", StringComparison.OrdinalIgnoreCase)) return DateTime.Today.ToShortDateString(); if (propertyName.Equals("TIME", StringComparison.OrdinalIgnoreCase)) return DateTime.Now.ToShortTimeString(); if (propertyName.Equals("ProductName", StringComparison.OrdinalIgnoreCase)) return MessageService.ProductName; if (propertyName.Equals("GUID", StringComparison.OrdinalIgnoreCase)) return Guid.NewGuid().ToString().ToUpperInvariant(); if (propertyName.Equals("USER", StringComparison.OrdinalIgnoreCase)) return Environment.UserName; if (propertyName.Equals("Version", StringComparison.OrdinalIgnoreCase)) return RevisionClass.FullVersion; if (propertyName.Equals("CONFIGDIRECTORY", StringComparison.OrdinalIgnoreCase)) return PropertyService.ConfigDirectory; foreach (IStringTagProvider provider in stringTagProviders) { string result = provider.ProvideString(propertyName, customTags); if (result != null) return result; } return null; } else { // it's a prefixed property // res: properties are quite common, so optimize by testing for them first // before allocaing the prefix/propertyName strings // All other prefixed properties {prefix:Key} shoulg get handled in the switch below. if (propertyName.StartsWith("res:", StringComparison.OrdinalIgnoreCase)) { try { return Parse(ResourceService.GetString(propertyName.Substring(4)), customTags); } catch (ResourceNotFoundException) { return null; } } string prefix = propertyName.Substring(0, k); propertyName = propertyName.Substring(k + 1); switch (prefix.ToUpperInvariant()) { case "SDKTOOLPATH": return FileUtility.GetSdkPath(propertyName); case "ADDINPATH": foreach (AddIn addIn in AddInTree.AddIns) { if (addIn.Manifest.Identities.ContainsKey(propertyName)) { return System.IO.Path.GetDirectoryName(addIn.FileName); } } return null; case "DATE": try { return DateTime.Now.ToString(propertyName, CultureInfo.CurrentCulture); } catch (Exception ex) { return ex.Message; } case "ENV": return Environment.GetEnvironmentVariable(propertyName); case "PROPERTY": return GetProperty(propertyName); default: IStringTagProvider provider; if (prefixedStringTagProviders.TryGetValue(prefix, out provider)) return provider.ProvideString(propertyName, customTags); else return null; } } } /// /// Allow special syntax to retrieve property values: /// ${property:PropertyName} /// ${property:PropertyName??DefaultValue} /// ${property:ContainerName/PropertyName} /// ${property:ContainerName/PropertyName??DefaultValue} /// A container is a Properties instance stored in the PropertyService. This is /// used by many AddIns to group all their properties into one container. /// static string GetProperty(string propertyName) { string defaultValue = ""; int pos = propertyName.LastIndexOf("??", StringComparison.Ordinal); if (pos >= 0) { defaultValue = propertyName.Substring(pos + 2); propertyName = propertyName.Substring(0, pos); } pos = propertyName.IndexOf('/'); if (pos >= 0) { Properties properties = PropertyService.Get(propertyName.Substring(0, pos), new Properties()); propertyName = propertyName.Substring(pos + 1); pos = propertyName.IndexOf('/'); while (pos >= 0) { properties = properties.Get(propertyName.Substring(0, pos), new Properties()); propertyName = propertyName.Substring(pos + 1); } return properties.Get(propertyName, defaultValue); } else { return PropertyService.Get(propertyName, defaultValue); } } } public struct StringTagPair { readonly string tag; readonly string value; public string Tag { get { return tag; } } public string Value { get { return value; } } public StringTagPair(string tag, string value) { if (tag == null) throw new ArgumentNullException("tag"); if (value == null) throw new ArgumentNullException("value"); this.tag = tag; this.value = value; } } }