// // AddinDescription.cs // // Author: // Lluis Sanchez Gual // // Copyright (C) 2007 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Xml; using System.Xml.Serialization; using System.Collections.Specialized; using Mono.Addins.Serialization; using Mono.Addins.Database; using System.Text; namespace Mono.Addins.Description { /// /// An add-in description /// /// /// This class represent an add-in manifest. It has properties for getting /// all information, and methods for loading and saving files. /// public class AddinDescription: IBinaryXmlElement { XmlDocument configDoc; string configFile; AddinDatabase ownerDatabase; string id; string name; string ns; string version; string compatVersion; string author; string url; string copyright; string description; string category; string basePath; string sourceAddinFile; bool isroot; bool hasUserId; bool canWrite = true; bool defaultEnabled = true; AddinFlags flags = AddinFlags.None; string domain; ModuleDescription mainModule; ModuleCollection optionalModules; ExtensionNodeSetCollection nodeSets; ConditionTypeDescriptionCollection conditionTypes; ExtensionPointCollection extensionPoints; ExtensionNodeDescription localizer; object[] fileInfo; AddinPropertyCollectionImpl properties; Dictionary variables; internal static BinaryXmlTypeMap typeMap; static AddinDescription () { typeMap = new BinaryXmlTypeMap (); typeMap.RegisterType (typeof(AddinDescription), "AddinDescription"); typeMap.RegisterType (typeof(Extension), "Extension"); typeMap.RegisterType (typeof(ExtensionNodeDescription), "Node"); typeMap.RegisterType (typeof(ExtensionNodeSet), "NodeSet"); typeMap.RegisterType (typeof(ExtensionNodeType), "NodeType"); typeMap.RegisterType (typeof(ExtensionPoint), "ExtensionPoint"); typeMap.RegisterType (typeof(ModuleDescription), "ModuleDescription"); typeMap.RegisterType (typeof(ConditionTypeDescription), "ConditionType"); typeMap.RegisterType (typeof(Condition), "Condition"); typeMap.RegisterType (typeof(AddinDependency), "AddinDependency"); typeMap.RegisterType (typeof(AssemblyDependency), "AssemblyDependency"); typeMap.RegisterType (typeof(NodeTypeAttribute), "NodeTypeAttribute"); typeMap.RegisterType (typeof(AddinFileInfo), "FileInfo"); typeMap.RegisterType (typeof(AddinProperty), "Property"); } internal AddinDatabase OwnerDatabase { get { return ownerDatabase; } set { ownerDatabase = value; } } /// /// Gets or sets the path to the main addin file. /// /// /// The addin file. /// /// /// The add-in file can be either the main assembly of an add-in or an xml manifest. /// public string AddinFile { get { return sourceAddinFile; } set { sourceAddinFile = value; } } /// /// Gets the addin identifier. /// /// /// The addin identifier. /// public string AddinId { get { return Addin.GetFullId (Namespace, LocalId, Version); } } /// /// Gets or sets the local identifier. /// /// /// The local identifier. /// public string LocalId { get { return id != null ? ParseString (id) : string.Empty; } set { id = value; hasUserId = true; } } /// /// Gets or sets the namespace. /// /// /// The namespace. /// public string Namespace { get { return ns != null ? ParseString (ns) : string.Empty; } set { ns = value; } } /// /// Gets or sets the display name of the add-in. /// /// /// The name. /// public string Name { get { string val = Properties.GetPropertyValue ("Name"); if (val.Length > 0) return val; if (name != null && name.Length > 0) return ParseString (name); if (HasUserId) return AddinId; else if (sourceAddinFile != null) return Path.GetFileNameWithoutExtension (sourceAddinFile); else return string.Empty; } set { name = value; } } /// /// Gets or sets the version. /// /// /// The version. /// public string Version { get { return version != null ? ParseString (version) : string.Empty; } set { version = value; } } /// /// Gets or sets the version of the add-in with which this add-in is backwards compatible. /// /// /// The compat version. /// public string CompatVersion { get { return compatVersion != null ? ParseString (compatVersion) : string.Empty; } set { compatVersion = value; } } /// /// Gets or sets the author. /// /// /// The author. /// public string Author { get { string val = Properties.GetPropertyValue ("Author"); if (val.Length > 0) return val; return ParseString (author) ?? string.Empty; } set { author = value; } } /// /// Gets or sets the Url where more information about the add-in can be found. /// /// /// The URL. /// public string Url { get { string val = Properties.GetPropertyValue ("Url"); if (val.Length > 0) return val; return ParseString (url) ?? string.Empty; } set { url = value; } } /// /// Gets or sets the copyright. /// /// /// The copyright. /// public string Copyright { get { string val = Properties.GetPropertyValue ("Copyright"); if (val.Length > 0) return val; return ParseString (copyright) ?? string.Empty; } set { copyright = value; } } /// /// Gets or sets the description of the add-in. /// /// /// The description. /// public string Description { get { string val = Properties.GetPropertyValue ("Description"); if (val.Length > 0) return val; return ParseString (description) ?? string.Empty; } set { description = value; } } /// /// Gets or sets the category of the add-in. /// /// /// The category. /// public string Category { get { string val = Properties.GetPropertyValue ("Category"); if (val.Length > 0) return val; return ParseString (category) ?? string.Empty; } set { category = value; } } /// /// Gets the base path for locating external files relative to the add-in. /// /// /// The base path. /// public string BasePath { get { return basePath != null ? basePath : string.Empty; } } internal void SetBasePath (string path) { basePath = path; } /// /// Gets or sets a value indicating whether this instance is an add-in root. /// /// /// true if this instance is an add-in root; otherwise, false. /// public bool IsRoot { get { return isroot; } set { isroot = value; } } /// /// Gets or sets a value indicating whether this add-in is enabled by default. /// /// /// true if enabled by default; otherwise, false. /// public bool EnabledByDefault { get { return defaultEnabled; } set { defaultEnabled = value; } } /// /// Gets or sets the add-in flags. /// /// /// The flags. /// public AddinFlags Flags { get { return flags; } set { flags = value; } } internal bool HasUserId { get { return hasUserId; } set { hasUserId = value; } } /// /// Gets a value indicating whether this add-in can be disabled. /// /// /// true if this add-in can be disabled; otherwise, false. /// public bool CanDisable { get { return (flags & AddinFlags.CantDisable) == 0 && !IsHidden; } } /// /// Gets a value indicating whether this add-in can be uninstalled. /// /// /// true if this instance can be uninstalled; otherwise, false. /// public bool CanUninstall { get { return (flags & AddinFlags.CantUninstall) == 0 && !IsHidden; } } /// /// Gets a value indicating whether this add-in is hidden. /// /// /// true if this add-in is hidden; otherwise, false. /// public bool IsHidden { get { return (flags & AddinFlags.Hidden) != 0; } } internal bool SupportsVersion (string ver) { return Addin.CompareVersions (ver, Version) >= 0 && (CompatVersion.Length == 0 || Addin.CompareVersions (ver, CompatVersion) <= 0); } /// /// Gets all external files /// /// /// All files. /// /// /// External files are data files and assemblies explicitly referenced in the Runtime section of the add-in manifest. /// public StringCollection AllFiles { get { StringCollection col = new StringCollection (); foreach (string s in MainModule.AllFiles) col.Add (s); foreach (ModuleDescription mod in OptionalModules) { foreach (string s in mod.AllFiles) col.Add (s); } return col; } } /// /// Gets all paths to be ignored by the add-in scanner. /// /// /// All paths to be ignored. /// public StringCollection AllIgnorePaths { get { StringCollection col = new StringCollection (); foreach (string s in MainModule.IgnorePaths) col.Add (s); foreach (ModuleDescription mod in OptionalModules) { foreach (string s in mod.IgnorePaths) col.Add (s); } return col; } } /// /// Gets the main module. /// /// /// The main module. /// public ModuleDescription MainModule { get { if (mainModule == null) { if (RootElement == null) mainModule = new ModuleDescription (); else mainModule = new ModuleDescription (RootElement); mainModule.SetParent (this); } return mainModule; } } /// /// Gets the optional modules. /// /// /// The optional modules. /// /// /// Optional modules can be used to declare extensions which will be registered only if some specified /// add-in dependencies can be satisfied. Dependencies specified in optional modules are 'soft dependencies', /// which means that they don't need to be satisfied in order to load the add-in. /// public ModuleCollection OptionalModules { get { if (optionalModules == null) { optionalModules = new ModuleCollection (this); if (RootElement != null) { foreach (XmlElement mod in RootElement.SelectNodes ("Module")) optionalModules.Add (new ModuleDescription (mod)); } } return optionalModules; } } /// /// Gets all modules (including the main module and all optional modules) /// /// /// All modules. /// public ModuleCollection AllModules { get { ModuleCollection col = new ModuleCollection (this); col.Add (MainModule); foreach (ModuleDescription mod in OptionalModules) col.Add (mod); return col; } } /// /// Gets the extension node sets. /// /// /// The extension node sets. /// public ExtensionNodeSetCollection ExtensionNodeSets { get { if (nodeSets == null) { nodeSets = new ExtensionNodeSetCollection (this); if (RootElement != null) { foreach (XmlElement elem in RootElement.SelectNodes ("ExtensionNodeSet")) nodeSets.Add (new ExtensionNodeSet (elem)); } } return nodeSets; } } /// /// Gets the extension points. /// /// /// The extension points. /// public ExtensionPointCollection ExtensionPoints { get { if (extensionPoints == null) { extensionPoints = new ExtensionPointCollection (this); if (RootElement != null) { foreach (XmlElement elem in RootElement.SelectNodes ("ExtensionPoint")) extensionPoints.Add (new ExtensionPoint (elem)); } } return extensionPoints; } } /// /// Gets the condition types. /// /// /// The condition types. /// public ConditionTypeDescriptionCollection ConditionTypes { get { if (conditionTypes == null) { conditionTypes = new ConditionTypeDescriptionCollection (this); if (RootElement != null) { foreach (XmlElement elem in RootElement.SelectNodes ("ConditionType")) conditionTypes.Add (new ConditionTypeDescription (elem)); } } return conditionTypes; } } /// /// Gets or sets the add-in localizer. /// /// /// The description of the add-in localizer for this add-in. /// public ExtensionNodeDescription Localizer { get { return localizer; } set { localizer = value; } } /// /// Custom properties specified in the add-in header /// public AddinPropertyCollection Properties { get { if (properties == null) properties = new AddinPropertyCollectionImpl (this); return properties; } } /// /// Adds an extension point. /// /// /// The extension point. /// /// /// Path that identifies the new extension point. /// public ExtensionPoint AddExtensionPoint (string path) { ExtensionPoint ep = new ExtensionPoint (); ep.Path = path; ExtensionPoints.Add (ep); return ep; } internal ExtensionNodeDescription FindExtensionNode (string path, bool lookInDeps) { // Look in the extensions of this add-in foreach (Extension ext in MainModule.Extensions) { if (path.StartsWith (ext.Path + "/")) { string subp = path.Substring (ext.Path.Length).Trim ('/'); ExtensionNodeDescriptionCollection nodes = ext.ExtensionNodes; ExtensionNodeDescription node = null; foreach (string p in subp.Split ('/')) { if (p.Length == 0) continue; node = nodes [p]; if (node == null) break; nodes = node.ChildNodes; } if (node != null) return node; } } if (!lookInDeps || OwnerDatabase == null) return null; // Look in dependencies foreach (Dependency dep in MainModule.Dependencies) { AddinDependency adep = dep as AddinDependency; if (adep == null) continue; Addin ad = OwnerDatabase.GetInstalledAddin (Domain, adep.FullAddinId); if (ad != null && ad.Description != null) { ExtensionNodeDescription node = ad.Description.FindExtensionNode (path, false); if (node != null) return node; } } return null; } XmlElement RootElement { get { if (configDoc != null) return configDoc.DocumentElement; else return null; } } internal void ResetXmlDoc () { configDoc = null; } /// /// Gets or sets file where this description is stored /// /// /// The file path. /// public string FileName { get { return configFile; } set { configFile = value; } } internal string Domain { get { return domain; } set { domain = value; } } internal void StoreFileInfo () { var allFiles = AllFiles; var list = new List (allFiles.Count); foreach (string f in allFiles) { string file = Path.Combine (this.BasePath, f); AddinFileInfo fi = new AddinFileInfo (); fi.FileName = f; fi.Timestamp = File.GetLastWriteTime (file); list.Add (fi); } fileInfo = list.ToArray (); } internal bool FilesChanged () { // Checks if the files of the add-in have changed. if (fileInfo == null) return true; foreach (AddinFileInfo f in fileInfo) { string file = Path.Combine (this.BasePath, f.FileName); if (!File.Exists (file)) return true; if (f.Timestamp != File.GetLastWriteTime (file)) return true; } return false; } void TransferCoreProperties (bool removeProperties) { if (properties == null) return; string val = properties.ExtractCoreProperty ("Id", removeProperties); if (val != null) id = val; val = properties.ExtractCoreProperty ("Namespace", removeProperties); if (val != null) ns = val; val = properties.ExtractCoreProperty ("Version", removeProperties); if (val != null) version = val; val = properties.ExtractCoreProperty ("CompatVersion", removeProperties); if (val != null) compatVersion = val; val = properties.ExtractCoreProperty ("DefaultEnabled", removeProperties); if (val != null) defaultEnabled = GetBool (val, true); val = properties.ExtractCoreProperty ("IsRoot", removeProperties); if (val != null) isroot = GetBool (val, true); val = properties.ExtractCoreProperty ("Flags", removeProperties); if (val != null) flags = (AddinFlags) Enum.Parse (typeof(AddinFlags), val); } bool TryGetVariableValue (string name, out string value) { if (variables != null && variables.TryGetValue (name, out value)) return true; switch (name) { case "Id": value = id; return true; case "Namespace": value = ns; return true; case "Version": value = version; return true; case "CompatVersion": value = compatVersion; return true; case "DefaultEnabled": value = defaultEnabled.ToString (); return true; case "IsRoot": value = isroot.ToString (); return true; case "Flags": value = flags.ToString (); return true; } if (properties != null && properties.HasProperty (name)) { value = properties.GetPropertyValue (name); return true; } value = null; return false; } /// /// Saves the add-in description. /// /// /// File name where to save this instance /// /// /// Saves the add-in description to the specified file and sets the FileName property. /// public void Save (string fileName) { configFile = fileName; Save (); } /// /// Saves the add-in description. /// /// /// It is thrown if FileName is not set /// /// /// The description is saved to the file specified in the FileName property. /// public void Save () { if (configFile == null) throw new InvalidOperationException ("File name not specified."); SaveXml (); using (StreamWriter sw = new StreamWriter (configFile)) { XmlTextWriter tw = new XmlTextWriter (sw); tw.Formatting = Formatting.Indented; configDoc.Save (tw); } } /// /// Generates an XML representation of the add-in description /// /// /// An XML manifest. /// public XmlDocument SaveToXml () { SaveXml (); return configDoc; } void SaveXml () { if (!canWrite) throw new InvalidOperationException ("Can't write incomplete description."); XmlElement elem; if (configDoc == null) { configDoc = new XmlDocument (); configDoc.AppendChild (configDoc.CreateElement ("Addin")); } elem = configDoc.DocumentElement; SaveCoreProperty (elem, HasUserId ? id : null, "id", "Id"); SaveCoreProperty (elem, version, "version", "Version"); SaveCoreProperty (elem, ns, "namespace", "Namespace"); SaveCoreProperty (elem, isroot ? "true" : null, "isroot", "IsRoot"); // Name will return the file name when HasUserId=false if (!string.IsNullOrEmpty (name)) elem.SetAttribute ("name", name); else elem.RemoveAttribute ("name"); SaveCoreProperty (elem, compatVersion, "compatVersion", "CompatVersion"); SaveCoreProperty (elem, defaultEnabled ? null : "false", "defaultEnabled", "DefaultEnabled"); SaveCoreProperty (elem, flags != AddinFlags.None ? flags.ToString () : null, "flags", "Flags"); if (author != null && author.Length > 0) elem.SetAttribute ("author", author); else elem.RemoveAttribute ("author"); if (url != null && url.Length > 0) elem.SetAttribute ("url", url); else elem.RemoveAttribute ("url"); if (copyright != null && copyright.Length > 0) elem.SetAttribute ("copyright", copyright); else elem.RemoveAttribute ("copyright"); if (description != null && description.Length > 0) elem.SetAttribute ("description", description); else elem.RemoveAttribute ("description"); if (category != null && category.Length > 0) elem.SetAttribute ("category", category); else elem.RemoveAttribute ("category"); if (localizer == null || localizer.Element == null) { // Remove old element if it exists XmlElement oldLoc = (XmlElement) elem.SelectSingleNode ("Localizer"); if (oldLoc != null) elem.RemoveChild (oldLoc); } if (localizer != null) localizer.SaveXml (elem); if (mainModule != null) { mainModule.Element = elem; mainModule.SaveXml (elem); } if (optionalModules != null) optionalModules.SaveXml (elem); if (nodeSets != null) nodeSets.SaveXml (elem); if (extensionPoints != null) extensionPoints.SaveXml (elem); XmlElement oldHeader = (XmlElement) elem.SelectSingleNode ("Header"); if (properties == null || properties.Count == 0) { if (oldHeader != null) elem.RemoveChild (oldHeader); } else { if (oldHeader == null) { oldHeader = elem.OwnerDocument.CreateElement ("Header"); if (elem.FirstChild != null) elem.InsertBefore (oldHeader, elem.FirstChild); else elem.AppendChild (oldHeader); } else oldHeader.RemoveAll (); foreach (var prop in properties) { XmlElement propElem = elem.OwnerDocument.CreateElement (prop.Name); if (!string.IsNullOrEmpty (prop.Locale)) propElem.SetAttribute ("locale", prop.Locale); propElem.InnerText = prop.Value ?? string.Empty; oldHeader.AppendChild (propElem); } } XmlElement oldVars = (XmlElement) elem.SelectSingleNode ("Variables"); if (variables == null || variables.Count == 0) { if (oldVars != null) elem.RemoveChild (oldVars); } else { if (oldVars == null) { oldVars = elem.OwnerDocument.CreateElement ("Variables"); if (elem.FirstChild != null) elem.InsertBefore (oldVars, elem.FirstChild); else elem.AppendChild (oldVars); } else oldVars.RemoveAll (); foreach (var prop in variables) { XmlElement propElem = elem.OwnerDocument.CreateElement (prop.Key); propElem.InnerText = prop.Value ?? string.Empty; oldVars.AppendChild (propElem); } } } public XmlDocument SaveToVsixXml () { if (!canWrite) throw new InvalidOperationException ("Can't write incomplete description."); XmlElement packageManifestEl; var vsixDoc = new XmlDocument (); vsixDoc.AppendChild (vsixDoc.CreateElement ("PackageManifest")); packageManifestEl = vsixDoc.DocumentElement; packageManifestEl.SetAttribute ("Version", "2.0.0"); packageManifestEl.SetAttribute ("xmlns", "http://schemas.microsoft.com/developer/vsx-schema/2011"); var metadata = vsixDoc.CreateElement ("Metadata"); var identity = vsixDoc.CreateElement ("Identity"); identity.SetAttribute ("Language", "en-US"); identity.SetAttribute ("Id", LocalId); identity.SetAttribute ("Version", Version); identity.SetAttribute ("Publisher", Properties.GetPropertyValue ("VisualStudio.Publisher")); metadata.AppendChild (identity); var displayNameEl = vsixDoc.CreateElement ("DisplayName"); displayNameEl.InnerText = Name; metadata.AppendChild (displayNameEl); var descriptionEl = vsixDoc.CreateElement ("Description"); descriptionEl.SetAttribute ("xml:space", "preserve"); descriptionEl.InnerText = Description; metadata.AppendChild (descriptionEl); var moreInfoEl = vsixDoc.CreateElement ("MoreInfo"); moreInfoEl.InnerText = Url; metadata.AppendChild (moreInfoEl); var tagsEl = vsixDoc.CreateElement ("Tags"); if (!string.IsNullOrEmpty (Properties.GetPropertyValue ("VisualStudio.Tags"))) tagsEl.InnerText = Properties.GetPropertyValue ("VisualStudio.Tags"); metadata.AppendChild (tagsEl); var categoriesEl = vsixDoc.CreateElement ("Categories"); categoriesEl.InnerText = Category; metadata.AppendChild (categoriesEl); var galleryFlagsEl = vsixDoc.CreateElement ("GalleryFlags"); var galleryFlags = Properties.GetPropertyValue ("VisualStudio.GalleryFlags"); if (string.IsNullOrEmpty (galleryFlags)) galleryFlags = "Public"; galleryFlagsEl.InnerText = galleryFlags; metadata.AppendChild (galleryFlagsEl); var badgesEl = vsixDoc.CreateElement ("Badges"); //TODO:Add Badges support metadata.AppendChild (badgesEl); var icon = Properties.GetPropertyValue ("Icon32"); if (!string.IsNullOrEmpty (icon)) { var iconEl = vsixDoc.CreateElement ("Icon"); iconEl.InnerText = icon; metadata.AppendChild (iconEl); } var license = Copyright; if (!string.IsNullOrEmpty (license)) { var licenseEl = vsixDoc.CreateElement ("License"); licenseEl.InnerText = license; metadata.AppendChild (licenseEl); } packageManifestEl.AppendChild (metadata); var installationEl = vsixDoc.CreateElement ("Installation"); var installationTargetEl = vsixDoc.CreateElement ("InstallationTarget"); installationTargetEl.SetAttribute ("Id", "Microsoft.VisualStudio.Mac"); installationEl.AppendChild (installationTargetEl); packageManifestEl.AppendChild (installationEl); packageManifestEl.AppendChild (vsixDoc.CreateElement ("Dependencies")); var assetsEl = vsixDoc.CreateElement ("Assets"); var addinInfoAsset = vsixDoc.CreateElement ("Asset"); addinInfoAsset.SetAttribute ("Type", "Microsoft.VisualStudio.Mac.AddinInfo"); addinInfoAsset.SetAttribute ("Path", "addin.info"); addinInfoAsset.SetAttribute ("Addressable", "true"); assetsEl.AppendChild (addinInfoAsset); if (!string.IsNullOrEmpty (icon)) { var iconAsset = vsixDoc.CreateElement ("Asset"); iconAsset.SetAttribute ("Type", "Microsoft.VisualStudio.Services.Icons.Default"); iconAsset.SetAttribute ("Path", icon); iconAsset.SetAttribute ("Addressable", "true"); assetsEl.AppendChild (iconAsset); } var propertyToAssetTypeMappings = new Dictionary{ {"VisualStudio.License", "Microsoft.VisualStudio.Services.Content.License"}, {"VisualStudio.Details", "Microsoft.VisualStudio.Services.Content.Details"}, {"VisualStudio.Changelog", "Microsoft.VisualStudio.Services.Content.Changelog"}, }; foreach (var mapping in propertyToAssetTypeMappings) { if (!string.IsNullOrEmpty (Properties.GetPropertyValue (mapping.Key))) { var asset = vsixDoc.CreateElement ("Asset"); asset.SetAttribute ("Type", mapping.Value); asset.SetAttribute ("Path", icon); asset.SetAttribute ("Addressable", "true"); assetsEl.AppendChild (asset); } } packageManifestEl.AppendChild (assetsEl); return vsixDoc; } void SaveCoreProperty (XmlElement elem, string val, string attr, string prop) { if (properties != null && properties.HasProperty (prop)) { elem.RemoveAttribute (attr); if (!string.IsNullOrEmpty (val)) properties.SetPropertyValue (prop, val); else properties.RemoveProperty (prop); } else if (string.IsNullOrEmpty (val)) elem.RemoveAttribute (attr); else elem.SetAttribute (attr, val); } /// /// Load an add-in description from a file /// /// /// The file. /// public static AddinDescription Read (string configFile) { AddinDescription config; using (Stream s = File.OpenRead (configFile)) { config = Read (s, Path.GetDirectoryName (configFile)); } config.configFile = configFile; return config; } /// /// Load an add-in description from a stream /// /// /// The stream /// /// /// The path to be used to resolve relative file paths. /// public static AddinDescription Read (Stream stream, string basePath) { return Read (new StreamReader (stream), basePath); } /// /// Load an add-in description from a text reader /// /// /// The text reader /// /// /// The path to be used to resolve relative file paths. /// public static AddinDescription Read (TextReader reader, string basePath) { AddinDescription config = new AddinDescription (); try { config.configDoc = new XmlDocument (); config.configDoc.Load (reader); } catch (Exception ex) { throw new InvalidOperationException ("The add-in configuration file is invalid: " + ex.Message, ex); } XmlElement elem = config.configDoc.DocumentElement; if (elem.LocalName == "ExtensionModel") return config; XmlElement varsElem = (XmlElement) elem.SelectSingleNode ("Variables"); if (varsElem != null) { foreach (XmlNode node in varsElem.ChildNodes) { XmlElement prop = node as XmlElement; if (prop == null) continue; if (config.variables == null) config.variables = new Dictionary (); config.variables [prop.LocalName] = prop.InnerText; } } config.id = elem.GetAttribute ("id"); config.ns = elem.GetAttribute ("namespace"); config.name = elem.GetAttribute ("name"); config.version = elem.GetAttribute ("version"); config.compatVersion = elem.GetAttribute ("compatVersion"); config.author = elem.GetAttribute ("author"); config.url = elem.GetAttribute ("url"); config.copyright = elem.GetAttribute ("copyright"); config.description = elem.GetAttribute ("description"); config.category = elem.GetAttribute ("category"); config.basePath = elem.GetAttribute ("basePath"); config.domain = "global"; string s = elem.GetAttribute ("isRoot"); if (s.Length == 0) s = elem.GetAttribute ("isroot"); config.isroot = GetBool (s, false); config.defaultEnabled = GetBool (elem.GetAttribute ("defaultEnabled"), true); string prot = elem.GetAttribute ("flags"); if (prot.Length == 0) config.flags = AddinFlags.None; else config.flags = (AddinFlags) Enum.Parse (typeof(AddinFlags), prot); XmlElement localizerElem = (XmlElement) elem.SelectSingleNode ("Localizer"); if (localizerElem != null) config.localizer = new ExtensionNodeDescription (localizerElem); XmlElement headerElem = (XmlElement) elem.SelectSingleNode ("Header"); if (headerElem != null) { foreach (XmlNode node in headerElem.ChildNodes) { XmlElement prop = node as XmlElement; if (prop == null) continue; config.Properties.SetPropertyValue (prop.LocalName, prop.InnerText, prop.GetAttribute ("locale")); } } config.TransferCoreProperties (false); if (config.id.Length > 0) config.hasUserId = true; return config; } internal string ParseString (string input) { if (input == null || input.Length < 4) return input; int i = input.IndexOf ("$("); if (i == -1) return input; StringBuilder result = new StringBuilder (input.Length); result.Append (input, 0, i); while (i < input.Length) { if (input [i] == '$') { i++; if (i >= input.Length || input[i] != '(') { result.Append ('$'); continue; } i++; int start = i; while (i < input.Length && input [i] != ')') i++; string tag = input.Substring (start, i - start); string tagValue; if (TryGetVariableValue (tag, out tagValue)) result.Append (tagValue); else { result.Append ('$'); i = start - 1; } } else { result.Append (input [i]); } i++; } return result.ToString (); } static bool GetBool (string s, bool defval) { if (s.Length == 0) return defval; else return s == "true" || s == "yes"; } internal static AddinDescription ReadBinary (FileDatabase fdb, string configFile) { AddinDescription description = (AddinDescription) fdb.ReadSharedObject (configFile, typeMap); if (description != null) { description.FileName = configFile; description.canWrite = !fdb.IgnoreDescriptionData; } return description; } internal void SaveBinary (FileDatabase fdb, string file) { configFile = file; SaveBinary (fdb); } internal void SaveBinary (FileDatabase fdb) { if (!canWrite) throw new InvalidOperationException ("Can't write incomplete description."); fdb.WriteSharedObject (AddinFile, FileName, typeMap, this); // BinaryXmlReader.DumpFile (configFile); } /// /// Verify this instance. /// /// /// This method checks all the definitions in the description and returns a list of errors. /// If the returned list is empty, it means that the description is valid. /// public StringCollection Verify () { return Verify (new AddinFileSystemExtension ()); } internal StringCollection Verify (AddinFileSystemExtension fs) { StringCollection errors = new StringCollection (); if (IsRoot) { if (OptionalModules.Count > 0) errors.Add ("Root add-in hosts can't have optional modules."); } if (AddinId.Length == 0 || Version.Length == 0) { if (ExtensionPoints.Count > 0) errors.Add ("Add-ins which define new extension points must have an Id and Version."); } MainModule.Verify ("", errors); OptionalModules.Verify ("", errors); ExtensionNodeSets.Verify ("", errors); ExtensionPoints.Verify ("", errors); ConditionTypes.Verify ("", errors); foreach (ExtensionNodeSet nset in ExtensionNodeSets) { if (nset.Id.Length == 0) errors.Add ("Attribute 'id' can't be empty for global node sets."); } string bp = null; if (BasePath.Length > 0) bp = BasePath; else if (sourceAddinFile != null && sourceAddinFile.Length > 0) bp = Path.GetDirectoryName (AddinFile); else if (configFile != null && configFile.Length > 0) bp = Path.GetDirectoryName (configFile); if (bp != null) { foreach (string file in AllFiles) { string asmFile = Path.Combine (bp, Util.NormalizePath (file)); if (!fs.FileExists (asmFile)) errors.Add ("The file '" + asmFile + "' referenced in the manifest could not be found."); } } if (localizer != null && localizer.GetAttribute ("type").Length == 0) { errors.Add ("The attribute 'type' in the Location element is required."); } // Ensure that there are no duplicated properties if (properties != null) { HashSet props = new HashSet (); foreach (var prop in properties) { if (!props.Add (prop.Name + " " + prop.Locale)) errors.Add (string.Format ("Property {0} specified more than once", prop.Name + (prop.Locale != null ? " (" + prop.Locale + ")" : ""))); } } return errors; } internal void SetExtensionsAddinId (string addinId) { foreach (ExtensionPoint ep in ExtensionPoints) ep.SetExtensionsAddinId (addinId); foreach (ExtensionNodeSet ns in ExtensionNodeSets) ns.SetExtensionsAddinId (addinId); } internal void UnmergeExternalData (Hashtable addins) { // Removes extension types and extension sets coming from other add-ins. foreach (ExtensionPoint ep in ExtensionPoints) ep.UnmergeExternalData (AddinId, addins); foreach (ExtensionNodeSet ns in ExtensionNodeSets) ns.UnmergeExternalData (AddinId, addins); } internal void MergeExternalData (AddinDescription other) { // Removes extension types and extension sets coming from other add-ins. foreach (ExtensionPoint ep in other.ExtensionPoints) { ExtensionPoint tep = ExtensionPoints [ep.Path]; if (tep != null) tep.MergeWith (AddinId, ep); } foreach (ExtensionNodeSet ns in other.ExtensionNodeSets) { ExtensionNodeSet tns = ExtensionNodeSets [ns.Id]; if (tns != null) tns.MergeWith (AddinId, ns); } } internal bool IsExtensionModel { get { return RootElement.LocalName == "ExtensionModel"; } } internal static AddinDescription Merge (AddinDescription desc1, AddinDescription desc2) { if (!desc2.IsExtensionModel) { AddinDescription tmp = desc1; desc1 = desc2; desc2 = tmp; } ((AddinPropertyCollectionImpl)desc1.Properties).AddRange (desc2.Properties); desc1.ExtensionPoints.AddRange (desc2.ExtensionPoints); desc1.ExtensionNodeSets.AddRange (desc2.ExtensionNodeSets); desc1.ConditionTypes.AddRange (desc2.ConditionTypes); desc1.OptionalModules.AddRange (desc2.OptionalModules); foreach (string s in desc2.MainModule.Assemblies) desc1.MainModule.Assemblies.Add (s); foreach (string s in desc2.MainModule.DataFiles) desc1.MainModule.DataFiles.Add (s); desc1.MainModule.MergeWith (desc2.MainModule); return desc1; } void IBinaryXmlElement.Write (BinaryXmlWriter writer) { TransferCoreProperties (true); writer.WriteValue ("id", ParseString (id)); writer.WriteValue ("ns", ParseString (ns)); writer.WriteValue ("isroot", isroot); writer.WriteValue ("name", ParseString (name)); writer.WriteValue ("version", ParseString (version)); writer.WriteValue ("compatVersion", ParseString (compatVersion)); writer.WriteValue ("hasUserId", hasUserId); writer.WriteValue ("author", ParseString (author)); writer.WriteValue ("url", ParseString (url)); writer.WriteValue ("copyright", ParseString (copyright)); writer.WriteValue ("description", ParseString (description)); writer.WriteValue ("category", ParseString (category)); writer.WriteValue ("basePath", basePath); writer.WriteValue ("sourceAddinFile", sourceAddinFile); writer.WriteValue ("defaultEnabled", defaultEnabled); writer.WriteValue ("domain", domain); writer.WriteValue ("MainModule", MainModule); writer.WriteValue ("OptionalModules", OptionalModules); writer.WriteValue ("NodeSets", ExtensionNodeSets); writer.WriteValue ("ExtensionPoints", ExtensionPoints); writer.WriteValue ("ConditionTypes", ConditionTypes); writer.WriteValue ("FilesInfo", fileInfo); writer.WriteValue ("Localizer", localizer); writer.WriteValue ("flags", (int)flags); writer.WriteValue ("Properties", properties); } void IBinaryXmlElement.Read (BinaryXmlReader reader) { id = reader.ReadStringValue ("id"); ns = reader.ReadStringValue ("ns"); isroot = reader.ReadBooleanValue ("isroot"); name = reader.ReadStringValue ("name"); version = reader.ReadStringValue ("version"); compatVersion = reader.ReadStringValue ("compatVersion"); hasUserId = reader.ReadBooleanValue ("hasUserId"); author = reader.ReadStringValue ("author"); url = reader.ReadStringValue ("url"); copyright = reader.ReadStringValue ("copyright"); description = reader.ReadStringValue ("description"); category = reader.ReadStringValue ("category"); basePath = reader.ReadStringValue ("basePath"); sourceAddinFile = reader.ReadStringValue ("sourceAddinFile"); defaultEnabled = reader.ReadBooleanValue ("defaultEnabled"); domain = reader.ReadStringValue ("domain"); mainModule = (ModuleDescription) reader.ReadValue ("MainModule"); optionalModules = (ModuleCollection) reader.ReadValue ("OptionalModules", new ModuleCollection (this)); nodeSets = (ExtensionNodeSetCollection) reader.ReadValue ("NodeSets", new ExtensionNodeSetCollection (this)); extensionPoints = (ExtensionPointCollection) reader.ReadValue ("ExtensionPoints", new ExtensionPointCollection (this)); conditionTypes = (ConditionTypeDescriptionCollection) reader.ReadValue ("ConditionTypes", new ConditionTypeDescriptionCollection (this)); fileInfo = (object[]) reader.ReadValue ("FilesInfo", null); localizer = (ExtensionNodeDescription) reader.ReadValue ("Localizer"); flags = (AddinFlags) reader.ReadInt32Value ("flags"); properties = (AddinPropertyCollectionImpl) reader.ReadValue ("Properties", new AddinPropertyCollectionImpl (this)); if (mainModule != null) mainModule.SetParent (this); } } class AddinFileInfo: IBinaryXmlElement { string fileName; DateTime timestamp; public string FileName { get { return fileName; } set { fileName = value; } } public System.DateTime Timestamp { get { return timestamp; } set { timestamp = value; } } public void Read (BinaryXmlReader reader) { fileName = reader.ReadStringValue ("fileName"); timestamp = reader.ReadDateTimeValue ("timestamp"); } public void Write (BinaryXmlWriter writer) { writer.WriteValue ("fileName", fileName); writer.WriteValue ("timestamp", timestamp); } } }