1 // 2 // AddinDescription.cs 3 // 4 // Author: 5 // Lluis Sanchez Gual 6 // 7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com) 8 // 9 // Permission is hereby granted, free of charge, to any person obtaining 10 // a copy of this software and associated documentation files (the 11 // "Software"), to deal in the Software without restriction, including 12 // without limitation the rights to use, copy, modify, merge, publish, 13 // distribute, sublicense, and/or sell copies of the Software, and to 14 // permit persons to whom the Software is furnished to do so, subject to 15 // the following conditions: 16 // 17 // The above copyright notice and this permission notice shall be 18 // included in all copies or substantial portions of the Software. 19 // 20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 // 28 29 using System; 30 using System.Collections; 31 using System.Collections.Generic; 32 using System.IO; 33 using System.Xml; 34 using System.Xml.Serialization; 35 using System.Collections.Specialized; 36 using Mono.Addins.Serialization; 37 using Mono.Addins.Database; 38 using System.Text; 39 40 namespace Mono.Addins.Description 41 { 42 /// <summary> 43 /// An add-in description 44 /// </summary> 45 /// <remarks> 46 /// This class represent an add-in manifest. It has properties for getting 47 /// all information, and methods for loading and saving files. 48 /// </remarks> 49 public class AddinDescription: IBinaryXmlElement 50 { 51 XmlDocument configDoc; 52 string configFile; 53 AddinDatabase ownerDatabase; 54 55 string id; 56 string name; 57 string ns; 58 string version; 59 string compatVersion; 60 string author; 61 string url; 62 string copyright; 63 string description; 64 string category; 65 string basePath; 66 string sourceAddinFile; 67 bool isroot; 68 bool hasUserId; 69 bool canWrite = true; 70 bool defaultEnabled = true; 71 AddinFlags flags = AddinFlags.None; 72 string domain; 73 74 ModuleDescription mainModule; 75 ModuleCollection optionalModules; 76 ExtensionNodeSetCollection nodeSets; 77 ConditionTypeDescriptionCollection conditionTypes; 78 ExtensionPointCollection extensionPoints; 79 ExtensionNodeDescription localizer; 80 object[] fileInfo; 81 82 AddinPropertyCollectionImpl properties; 83 Dictionary<string,string> variables; 84 85 internal static BinaryXmlTypeMap typeMap; 86 AddinDescription()87 static AddinDescription () 88 { 89 typeMap = new BinaryXmlTypeMap (); 90 typeMap.RegisterType (typeof(AddinDescription), "AddinDescription"); 91 typeMap.RegisterType (typeof(Extension), "Extension"); 92 typeMap.RegisterType (typeof(ExtensionNodeDescription), "Node"); 93 typeMap.RegisterType (typeof(ExtensionNodeSet), "NodeSet"); 94 typeMap.RegisterType (typeof(ExtensionNodeType), "NodeType"); 95 typeMap.RegisterType (typeof(ExtensionPoint), "ExtensionPoint"); 96 typeMap.RegisterType (typeof(ModuleDescription), "ModuleDescription"); 97 typeMap.RegisterType (typeof(ConditionTypeDescription), "ConditionType"); 98 typeMap.RegisterType (typeof(Condition), "Condition"); 99 typeMap.RegisterType (typeof(AddinDependency), "AddinDependency"); 100 typeMap.RegisterType (typeof(AssemblyDependency), "AssemblyDependency"); 101 typeMap.RegisterType (typeof(NodeTypeAttribute), "NodeTypeAttribute"); 102 typeMap.RegisterType (typeof(AddinFileInfo), "FileInfo"); 103 typeMap.RegisterType (typeof(AddinProperty), "Property"); 104 } 105 106 internal AddinDatabase OwnerDatabase { 107 get { return ownerDatabase; } 108 set { ownerDatabase = value; } 109 } 110 111 /// <summary> 112 /// Gets or sets the path to the main addin file. 113 /// </summary> 114 /// <value> 115 /// The addin file. 116 /// </value> 117 /// <remarks> 118 /// The add-in file can be either the main assembly of an add-in or an xml manifest. 119 /// </remarks> 120 public string AddinFile { 121 get { return sourceAddinFile; } 122 set { sourceAddinFile = value; } 123 } 124 125 /// <summary> 126 /// Gets the addin identifier. 127 /// </summary> 128 /// <value> 129 /// The addin identifier. 130 /// </value> 131 public string AddinId { 132 get { return Addin.GetFullId (Namespace, LocalId, Version); } 133 } 134 135 /// <summary> 136 /// Gets or sets the local identifier. 137 /// </summary> 138 /// <value> 139 /// The local identifier. 140 /// </value> 141 public string LocalId { 142 get { return id != null ? ParseString (id) : string.Empty; } 143 set { id = value; hasUserId = true; } 144 } 145 146 /// <summary> 147 /// Gets or sets the namespace. 148 /// </summary> 149 /// <value> 150 /// The namespace. 151 /// </value> 152 public string Namespace { 153 get { return ns != null ? ParseString (ns) : string.Empty; } 154 set { ns = value; } 155 } 156 157 /// <summary> 158 /// Gets or sets the display name of the add-in. 159 /// </summary> 160 /// <value> 161 /// The name. 162 /// </value> 163 public string Name { 164 get { 165 string val = Properties.GetPropertyValue ("Name"); 166 if (val.Length > 0) 167 return val; 168 if (name != null && name.Length > 0) 169 return ParseString (name); 170 if (HasUserId) 171 return AddinId; 172 else if (sourceAddinFile != null) 173 return Path.GetFileNameWithoutExtension (sourceAddinFile); 174 else 175 return string.Empty; 176 } 177 set { name = value; } 178 } 179 180 /// <summary> 181 /// Gets or sets the version. 182 /// </summary> 183 /// <value> 184 /// The version. 185 /// </value> 186 public string Version { 187 get { return version != null ? ParseString (version) : string.Empty; } 188 set { version = value; } 189 } 190 191 /// <summary> 192 /// Gets or sets the version of the add-in with which this add-in is backwards compatible. 193 /// </summary> 194 /// <value> 195 /// The compat version. 196 /// </value> 197 public string CompatVersion { 198 get { return compatVersion != null ? ParseString (compatVersion) : string.Empty; } 199 set { compatVersion = value; } 200 } 201 202 /// <summary> 203 /// Gets or sets the author. 204 /// </summary> 205 /// <value> 206 /// The author. 207 /// </value> 208 public string Author { 209 get { 210 string val = Properties.GetPropertyValue ("Author"); 211 if (val.Length > 0) 212 return val; 213 return ParseString (author) ?? string.Empty; 214 } 215 set { author = value; } 216 } 217 218 /// <summary> 219 /// Gets or sets the Url where more information about the add-in can be found. 220 /// </summary> 221 /// <value> 222 /// The URL. 223 /// </value> 224 public string Url { 225 get { 226 string val = Properties.GetPropertyValue ("Url"); 227 if (val.Length > 0) 228 return val; 229 return ParseString (url) ?? string.Empty; 230 } 231 set { url = value; } 232 } 233 234 /// <summary> 235 /// Gets or sets the copyright. 236 /// </summary> 237 /// <value> 238 /// The copyright. 239 /// </value> 240 public string Copyright { 241 get { 242 string val = Properties.GetPropertyValue ("Copyright"); 243 if (val.Length > 0) 244 return val; 245 return ParseString (copyright) ?? string.Empty; 246 } 247 set { copyright = value; } 248 } 249 250 /// <summary> 251 /// Gets or sets the description of the add-in. 252 /// </summary> 253 /// <value> 254 /// The description. 255 /// </value> 256 public string Description { 257 get { 258 string val = Properties.GetPropertyValue ("Description"); 259 if (val.Length > 0) 260 return val; 261 return ParseString (description) ?? string.Empty; 262 } 263 set { description = value; } 264 } 265 266 /// <summary> 267 /// Gets or sets the category of the add-in. 268 /// </summary> 269 /// <value> 270 /// The category. 271 /// </value> 272 public string Category { 273 get { 274 string val = Properties.GetPropertyValue ("Category"); 275 if (val.Length > 0) 276 return val; 277 return ParseString (category) ?? string.Empty; 278 } 279 set { category = value; } 280 } 281 282 /// <summary> 283 /// Gets the base path for locating external files relative to the add-in. 284 /// </summary> 285 /// <value> 286 /// The base path. 287 /// </value> 288 public string BasePath { 289 get { return basePath != null ? basePath : string.Empty; } 290 } 291 SetBasePath(string path)292 internal void SetBasePath (string path) 293 { 294 basePath = path; 295 } 296 297 /// <summary> 298 /// Gets or sets a value indicating whether this instance is an add-in root. 299 /// </summary> 300 /// <value> 301 /// <c>true</c> if this instance is an add-in root; otherwise, <c>false</c>. 302 /// </value> 303 public bool IsRoot { 304 get { return isroot; } 305 set { isroot = value; } 306 } 307 308 /// <summary> 309 /// Gets or sets a value indicating whether this add-in is enabled by default. 310 /// </summary> 311 /// <value> 312 /// <c>true</c> if enabled by default; otherwise, <c>false</c>. 313 /// </value> 314 public bool EnabledByDefault { 315 get { return defaultEnabled; } 316 set { defaultEnabled = value; } 317 } 318 319 /// <summary> 320 /// Gets or sets the add-in flags. 321 /// </summary> 322 /// <value> 323 /// The flags. 324 /// </value> 325 public AddinFlags Flags { 326 get { return flags; } 327 set { flags = value; } 328 } 329 330 internal bool HasUserId { 331 get { return hasUserId; } 332 set { hasUserId = value; } 333 } 334 335 /// <summary> 336 /// Gets a value indicating whether this add-in can be disabled. 337 /// </summary> 338 /// <value> 339 /// <c>true</c> if this add-in can be disabled; otherwise, <c>false</c>. 340 /// </value> 341 public bool CanDisable { 342 get { return (flags & AddinFlags.CantDisable) == 0 && !IsHidden; } 343 } 344 345 /// <summary> 346 /// Gets a value indicating whether this add-in can be uninstalled. 347 /// </summary> 348 /// <value> 349 /// <c>true</c> if this instance can be uninstalled; otherwise, <c>false</c>. 350 /// </value> 351 public bool CanUninstall { 352 get { return (flags & AddinFlags.CantUninstall) == 0 && !IsHidden; } 353 } 354 355 /// <summary> 356 /// Gets a value indicating whether this add-in is hidden. 357 /// </summary> 358 /// <value> 359 /// <c>true</c> if this add-in is hidden; otherwise, <c>false</c>. 360 /// </value> 361 public bool IsHidden { 362 get { return (flags & AddinFlags.Hidden) != 0; } 363 } 364 SupportsVersion(string ver)365 internal bool SupportsVersion (string ver) 366 { 367 return Addin.CompareVersions (ver, Version) >= 0 && 368 (CompatVersion.Length == 0 || Addin.CompareVersions (ver, CompatVersion) <= 0); 369 } 370 371 /// <summary> 372 /// Gets all external files 373 /// </summary> 374 /// <value> 375 /// All files. 376 /// </value> 377 /// <remarks> 378 /// External files are data files and assemblies explicitly referenced in the Runtime section of the add-in manifest. 379 /// </remarks> 380 public StringCollection AllFiles { 381 get { 382 StringCollection col = new StringCollection (); 383 foreach (string s in MainModule.AllFiles) 384 col.Add (s); 385 386 foreach (ModuleDescription mod in OptionalModules) { 387 foreach (string s in mod.AllFiles) 388 col.Add (s); 389 } 390 return col; 391 } 392 } 393 394 /// <summary> 395 /// Gets all paths to be ignored by the add-in scanner. 396 /// </summary> 397 /// <value> 398 /// All paths to be ignored. 399 /// </value> 400 public StringCollection AllIgnorePaths { 401 get { 402 StringCollection col = new StringCollection (); 403 foreach (string s in MainModule.IgnorePaths) 404 col.Add (s); 405 406 foreach (ModuleDescription mod in OptionalModules) { 407 foreach (string s in mod.IgnorePaths) 408 col.Add (s); 409 } 410 return col; 411 } 412 } 413 414 /// <summary> 415 /// Gets the main module. 416 /// </summary> 417 /// <value> 418 /// The main module. 419 /// </value> 420 public ModuleDescription MainModule { 421 get { 422 if (mainModule == null) { 423 if (RootElement == null) 424 mainModule = new ModuleDescription (); 425 else 426 mainModule = new ModuleDescription (RootElement); 427 mainModule.SetParent (this); 428 } 429 return mainModule; 430 } 431 } 432 433 /// <summary> 434 /// Gets the optional modules. 435 /// </summary> 436 /// <value> 437 /// The optional modules. 438 /// </value> 439 /// <remarks> 440 /// Optional modules can be used to declare extensions which will be registered only if some specified 441 /// add-in dependencies can be satisfied. Dependencies specified in optional modules are 'soft dependencies', 442 /// which means that they don't need to be satisfied in order to load the add-in. 443 /// </remarks> 444 public ModuleCollection OptionalModules { 445 get { 446 if (optionalModules == null) { 447 optionalModules = new ModuleCollection (this); 448 if (RootElement != null) { 449 foreach (XmlElement mod in RootElement.SelectNodes ("Module")) 450 optionalModules.Add (new ModuleDescription (mod)); 451 } 452 } 453 return optionalModules; 454 } 455 } 456 457 /// <summary> 458 /// Gets all modules (including the main module and all optional modules) 459 /// </summary> 460 /// <value> 461 /// All modules. 462 /// </value> 463 public ModuleCollection AllModules { 464 get { 465 ModuleCollection col = new ModuleCollection (this); 466 col.Add (MainModule); 467 foreach (ModuleDescription mod in OptionalModules) 468 col.Add (mod); 469 return col; 470 } 471 } 472 473 /// <summary> 474 /// Gets the extension node sets. 475 /// </summary> 476 /// <value> 477 /// The extension node sets. 478 /// </value> 479 public ExtensionNodeSetCollection ExtensionNodeSets { 480 get { 481 if (nodeSets == null) { 482 nodeSets = new ExtensionNodeSetCollection (this); 483 if (RootElement != null) { 484 foreach (XmlElement elem in RootElement.SelectNodes ("ExtensionNodeSet")) 485 nodeSets.Add (new ExtensionNodeSet (elem)); 486 } 487 } 488 return nodeSets; 489 } 490 } 491 492 /// <summary> 493 /// Gets the extension points. 494 /// </summary> 495 /// <value> 496 /// The extension points. 497 /// </value> 498 public ExtensionPointCollection ExtensionPoints { 499 get { 500 if (extensionPoints == null) { 501 extensionPoints = new ExtensionPointCollection (this); 502 if (RootElement != null) { 503 foreach (XmlElement elem in RootElement.SelectNodes ("ExtensionPoint")) 504 extensionPoints.Add (new ExtensionPoint (elem)); 505 } 506 } 507 return extensionPoints; 508 } 509 } 510 511 /// <summary> 512 /// Gets the condition types. 513 /// </summary> 514 /// <value> 515 /// The condition types. 516 /// </value> 517 public ConditionTypeDescriptionCollection ConditionTypes { 518 get { 519 if (conditionTypes == null) { 520 conditionTypes = new ConditionTypeDescriptionCollection (this); 521 if (RootElement != null) { 522 foreach (XmlElement elem in RootElement.SelectNodes ("ConditionType")) 523 conditionTypes.Add (new ConditionTypeDescription (elem)); 524 } 525 } 526 return conditionTypes; 527 } 528 } 529 530 /// <summary> 531 /// Gets or sets the add-in localizer. 532 /// </summary> 533 /// <value> 534 /// The description of the add-in localizer for this add-in. 535 /// </value> 536 public ExtensionNodeDescription Localizer { 537 get { return localizer; } 538 set { localizer = value; } 539 } 540 541 /// <summary> 542 /// Custom properties specified in the add-in header 543 /// </summary> 544 public AddinPropertyCollection Properties { 545 get { 546 if (properties == null) 547 properties = new AddinPropertyCollectionImpl (this); 548 return properties; 549 } 550 } 551 552 /// <summary> 553 /// Adds an extension point. 554 /// </summary> 555 /// <returns> 556 /// The extension point. 557 /// </returns> 558 /// <param name='path'> 559 /// Path that identifies the new extension point. 560 /// </param> AddExtensionPoint(string path)561 public ExtensionPoint AddExtensionPoint (string path) 562 { 563 ExtensionPoint ep = new ExtensionPoint (); 564 ep.Path = path; 565 ExtensionPoints.Add (ep); 566 return ep; 567 } 568 FindExtensionNode(string path, bool lookInDeps)569 internal ExtensionNodeDescription FindExtensionNode (string path, bool lookInDeps) 570 { 571 // Look in the extensions of this add-in 572 573 foreach (Extension ext in MainModule.Extensions) { 574 if (path.StartsWith (ext.Path + "/")) { 575 string subp = path.Substring (ext.Path.Length).Trim ('/'); 576 ExtensionNodeDescriptionCollection nodes = ext.ExtensionNodes; 577 ExtensionNodeDescription node = null; 578 foreach (string p in subp.Split ('/')) { 579 if (p.Length == 0) continue; 580 node = nodes [p]; 581 if (node == null) 582 break; 583 nodes = node.ChildNodes; 584 } 585 if (node != null) 586 return node; 587 } 588 } 589 590 if (!lookInDeps || OwnerDatabase == null) 591 return null; 592 593 // Look in dependencies 594 595 foreach (Dependency dep in MainModule.Dependencies) { 596 AddinDependency adep = dep as AddinDependency; 597 if (adep == null) continue; 598 Addin ad = OwnerDatabase.GetInstalledAddin (Domain, adep.FullAddinId); 599 if (ad != null && ad.Description != null) { 600 ExtensionNodeDescription node = ad.Description.FindExtensionNode (path, false); 601 if (node != null) 602 return node; 603 } 604 } 605 return null; 606 } 607 608 XmlElement RootElement { 609 get { 610 if (configDoc != null) 611 return configDoc.DocumentElement; 612 else 613 return null; 614 } 615 } 616 ResetXmlDoc()617 internal void ResetXmlDoc () 618 { 619 configDoc = null; 620 } 621 622 /// <summary> 623 /// Gets or sets file where this description is stored 624 /// </summary> 625 /// <value> 626 /// The file path. 627 /// </value> 628 public string FileName { 629 get { return configFile; } 630 set { configFile = value; } 631 } 632 633 internal string Domain { 634 get { return domain; } 635 set { domain = value; } 636 } 637 StoreFileInfo()638 internal void StoreFileInfo () 639 { 640 ArrayList list = new ArrayList (); 641 foreach (string f in AllFiles) { 642 string file = Path.Combine (this.BasePath, f); 643 AddinFileInfo fi = new AddinFileInfo (); 644 fi.FileName = f; 645 fi.Timestamp = File.GetLastWriteTime (file); 646 list.Add (fi); 647 } 648 fileInfo = list.ToArray (); 649 } 650 FilesChanged()651 internal bool FilesChanged () 652 { 653 // Checks if the files of the add-in have changed. 654 if (fileInfo == null) 655 return true; 656 657 foreach (AddinFileInfo f in fileInfo) { 658 string file = Path.Combine (this.BasePath, f.FileName); 659 if (!File.Exists (file)) 660 return true; 661 if (f.Timestamp != File.GetLastWriteTime (file)) 662 return true; 663 } 664 665 return false; 666 } 667 TransferCoreProperties(bool removeProperties)668 void TransferCoreProperties (bool removeProperties) 669 { 670 if (properties == null) 671 return; 672 673 string val = properties.ExtractCoreProperty ("Id", removeProperties); 674 if (val != null) 675 id = val; 676 677 val = properties.ExtractCoreProperty ("Namespace", removeProperties); 678 if (val != null) 679 ns = val; 680 681 val = properties.ExtractCoreProperty ("Version", removeProperties); 682 if (val != null) 683 version = val; 684 685 val = properties.ExtractCoreProperty ("CompatVersion", removeProperties); 686 if (val != null) 687 compatVersion = val; 688 689 val = properties.ExtractCoreProperty ("DefaultEnabled", removeProperties); 690 if (val != null) 691 defaultEnabled = GetBool (val, true); 692 693 val = properties.ExtractCoreProperty ("IsRoot", removeProperties); 694 if (val != null) 695 isroot = GetBool (val, true); 696 697 val = properties.ExtractCoreProperty ("Flags", removeProperties); 698 if (val != null) 699 flags = (AddinFlags) Enum.Parse (typeof(AddinFlags), val); 700 } 701 702 /// <summary> 703 /// Saves the add-in description. 704 /// </summary> 705 /// <param name='fileName'> 706 /// File name where to save this instance 707 /// </param> 708 /// <remarks> 709 /// Saves the add-in description to the specified file and sets the FileName property. 710 /// </remarks> Save(string fileName)711 public void Save (string fileName) 712 { 713 configFile = fileName; 714 Save (); 715 } 716 717 /// <summary> 718 /// Saves the add-in description. 719 /// </summary> 720 /// <exception cref='InvalidOperationException'> 721 /// It is thrown if FileName is not set 722 /// </exception> 723 /// <remarks> 724 /// The description is saved to the file specified in the FileName property. 725 /// </remarks> Save()726 public void Save () 727 { 728 if (configFile == null) 729 throw new InvalidOperationException ("File name not specified."); 730 731 SaveXml (); 732 733 using (StreamWriter sw = new StreamWriter (configFile)) { 734 XmlTextWriter tw = new XmlTextWriter (sw); 735 tw.Formatting = Formatting.Indented; 736 configDoc.Save (tw); 737 } 738 } 739 740 /// <summary> 741 /// Generates an XML representation of the add-in description 742 /// </summary> 743 /// <returns> 744 /// An XML manifest. 745 /// </returns> SaveToXml()746 public XmlDocument SaveToXml () 747 { 748 SaveXml (); 749 return configDoc; 750 } 751 SaveXml()752 void SaveXml () 753 { 754 if (!canWrite) 755 throw new InvalidOperationException ("Can't write incomplete description."); 756 757 XmlElement elem; 758 759 if (configDoc == null) { 760 configDoc = new XmlDocument (); 761 configDoc.AppendChild (configDoc.CreateElement ("Addin")); 762 } 763 764 elem = configDoc.DocumentElement; 765 766 SaveCoreProperty (elem, HasUserId ? id : null, "id", "Id"); 767 SaveCoreProperty (elem, version, "version", "Version"); 768 SaveCoreProperty (elem, ns, "namespace", "Namespace"); 769 SaveCoreProperty (elem, isroot ? "true" : null, "isroot", "IsRoot"); 770 771 // Name will return the file name when HasUserId=false 772 if (!string.IsNullOrEmpty (name)) 773 elem.SetAttribute ("name", name); 774 else 775 elem.RemoveAttribute ("name"); 776 777 SaveCoreProperty (elem, compatVersion, "compatVersion", "CompatVersion"); 778 SaveCoreProperty (elem, defaultEnabled ? null : "false", "defaultEnabled", "DefaultEnabled"); 779 SaveCoreProperty (elem, flags != AddinFlags.None ? flags.ToString () : null, "flags", "Flags"); 780 781 if (author != null && author.Length > 0) 782 elem.SetAttribute ("author", author); 783 else 784 elem.RemoveAttribute ("author"); 785 786 if (url != null && url.Length > 0) 787 elem.SetAttribute ("url", url); 788 else 789 elem.RemoveAttribute ("url"); 790 791 if (copyright != null && copyright.Length > 0) 792 elem.SetAttribute ("copyright", copyright); 793 else 794 elem.RemoveAttribute ("copyright"); 795 796 if (description != null && description.Length > 0) 797 elem.SetAttribute ("description", description); 798 else 799 elem.RemoveAttribute ("description"); 800 801 if (category != null && category.Length > 0) 802 elem.SetAttribute ("category", category); 803 else 804 elem.RemoveAttribute ("category"); 805 806 if (localizer == null || localizer.Element == null) { 807 // Remove old element if it exists 808 XmlElement oldLoc = (XmlElement) elem.SelectSingleNode ("Localizer"); 809 if (oldLoc != null) 810 elem.RemoveChild (oldLoc); 811 } 812 if (localizer != null) 813 localizer.SaveXml (elem); 814 815 if (mainModule != null) { 816 mainModule.Element = elem; 817 mainModule.SaveXml (elem); 818 } 819 820 if (optionalModules != null) 821 optionalModules.SaveXml (elem); 822 823 if (nodeSets != null) 824 nodeSets.SaveXml (elem); 825 826 if (extensionPoints != null) 827 extensionPoints.SaveXml (elem); 828 829 XmlElement oldHeader = (XmlElement) elem.SelectSingleNode ("Header"); 830 if (properties == null || properties.Count == 0) { 831 if (oldHeader != null) 832 elem.RemoveChild (oldHeader); 833 } else { 834 if (oldHeader == null) { 835 oldHeader = elem.OwnerDocument.CreateElement ("Header"); 836 if (elem.FirstChild != null) 837 elem.InsertBefore (oldHeader, elem.FirstChild); 838 else 839 elem.AppendChild (oldHeader); 840 } 841 else 842 oldHeader.RemoveAll (); 843 foreach (var prop in properties) { 844 XmlElement propElem = elem.OwnerDocument.CreateElement (prop.Name); 845 if (!string.IsNullOrEmpty (prop.Locale)) 846 propElem.SetAttribute ("locale", prop.Locale); 847 propElem.InnerText = prop.Value ?? string.Empty; 848 oldHeader.AppendChild (propElem); 849 } 850 } 851 852 XmlElement oldVars = (XmlElement) elem.SelectSingleNode ("Variables"); 853 if (variables == null || variables.Count == 0) { 854 if (oldVars != null) 855 elem.RemoveChild (oldVars); 856 } else { 857 if (oldVars == null) { 858 oldVars = elem.OwnerDocument.CreateElement ("Variables"); 859 if (elem.FirstChild != null) 860 elem.InsertBefore (oldVars, elem.FirstChild); 861 else 862 elem.AppendChild (oldVars); 863 } 864 else 865 oldVars.RemoveAll (); 866 foreach (var prop in variables) { 867 XmlElement propElem = elem.OwnerDocument.CreateElement (prop.Key); 868 propElem.InnerText = prop.Value ?? string.Empty; 869 oldVars.AppendChild (propElem); 870 } 871 } 872 } 873 SaveCoreProperty(XmlElement elem, string val, string attr, string prop)874 void SaveCoreProperty (XmlElement elem, string val, string attr, string prop) 875 { 876 if (properties != null && properties.HasProperty (prop)) { 877 elem.RemoveAttribute (attr); 878 if (!string.IsNullOrEmpty (val)) 879 properties.SetPropertyValue (prop, val); 880 else 881 properties.RemoveProperty (prop); 882 } 883 else if (string.IsNullOrEmpty (val)) 884 elem.RemoveAttribute (attr); 885 else 886 elem.SetAttribute (attr, val); 887 } 888 889 890 /// <summary> 891 /// Load an add-in description from a file 892 /// </summary> 893 /// <param name='configFile'> 894 /// The file. 895 /// </param> Read(string configFile)896 public static AddinDescription Read (string configFile) 897 { 898 AddinDescription config; 899 using (Stream s = File.OpenRead (configFile)) { 900 config = Read (s, Path.GetDirectoryName (configFile)); 901 } 902 config.configFile = configFile; 903 return config; 904 } 905 906 /// <summary> 907 /// Load an add-in description from a stream 908 /// </summary> 909 /// <param name='stream'> 910 /// The stream 911 /// </param> 912 /// <param name='basePath'> 913 /// The path to be used to resolve relative file paths. 914 /// </param> Read(Stream stream, string basePath)915 public static AddinDescription Read (Stream stream, string basePath) 916 { 917 return Read (new StreamReader (stream), basePath); 918 } 919 920 /// <summary> 921 /// Load an add-in description from a text reader 922 /// </summary> 923 /// <param name='reader'> 924 /// The text reader 925 /// </param> 926 /// <param name='basePath'> 927 /// The path to be used to resolve relative file paths. 928 /// </param> Read(TextReader reader, string basePath)929 public static AddinDescription Read (TextReader reader, string basePath) 930 { 931 AddinDescription config = new AddinDescription (); 932 933 try { 934 config.configDoc = new XmlDocument (); 935 config.configDoc.Load (reader); 936 } catch (Exception ex) { 937 throw new InvalidOperationException ("The add-in configuration file is invalid: " + ex.Message, ex); 938 } 939 940 XmlElement elem = config.configDoc.DocumentElement; 941 if (elem.LocalName == "ExtensionModel") 942 return config; 943 944 XmlElement varsElem = (XmlElement) elem.SelectSingleNode ("Variables"); 945 if (varsElem != null) { 946 foreach (XmlNode node in varsElem.ChildNodes) { 947 XmlElement prop = node as XmlElement; 948 if (prop == null) 949 continue; 950 if (config.variables == null) 951 config.variables = new Dictionary<string, string> (); 952 config.variables [prop.LocalName] = prop.InnerText; 953 } 954 } 955 956 config.id = elem.GetAttribute ("id"); 957 config.ns = elem.GetAttribute ("namespace"); 958 config.name = elem.GetAttribute ("name"); 959 config.version = elem.GetAttribute ("version"); 960 config.compatVersion = elem.GetAttribute ("compatVersion"); 961 config.author = elem.GetAttribute ("author"); 962 config.url = elem.GetAttribute ("url"); 963 config.copyright = elem.GetAttribute ("copyright"); 964 config.description = elem.GetAttribute ("description"); 965 config.category = elem.GetAttribute ("category"); 966 config.basePath = elem.GetAttribute ("basePath"); 967 config.domain = "global"; 968 969 string s = elem.GetAttribute ("isRoot"); 970 if (s.Length == 0) s = elem.GetAttribute ("isroot"); 971 config.isroot = GetBool (s, false); 972 973 config.defaultEnabled = GetBool (elem.GetAttribute ("defaultEnabled"), true); 974 975 string prot = elem.GetAttribute ("flags"); 976 if (prot.Length == 0) 977 config.flags = AddinFlags.None; 978 else 979 config.flags = (AddinFlags) Enum.Parse (typeof(AddinFlags), prot); 980 981 XmlElement localizerElem = (XmlElement) elem.SelectSingleNode ("Localizer"); 982 if (localizerElem != null) 983 config.localizer = new ExtensionNodeDescription (localizerElem); 984 985 XmlElement headerElem = (XmlElement) elem.SelectSingleNode ("Header"); 986 if (headerElem != null) { 987 foreach (XmlNode node in headerElem.ChildNodes) { 988 XmlElement prop = node as XmlElement; 989 if (prop == null) 990 continue; 991 config.Properties.SetPropertyValue (prop.LocalName, prop.InnerText, prop.GetAttribute ("locale")); 992 } 993 } 994 995 config.TransferCoreProperties (false); 996 997 if (config.id.Length > 0) 998 config.hasUserId = true; 999 1000 return config; 1001 } 1002 ParseString(string input)1003 internal string ParseString (string input) 1004 { 1005 if (input == null || input.Length < 4 || variables == null || variables.Count == 0) 1006 return input; 1007 1008 int i = input.IndexOf ("$("); 1009 if (i == -1) 1010 return input; 1011 1012 StringBuilder result = new StringBuilder (input.Length); 1013 result.Append (input, 0, i); 1014 1015 while (i < input.Length) { 1016 if (input [i] == '$') { 1017 i++; 1018 1019 if (i >= input.Length || input[i] != '(') { 1020 result.Append ('$'); 1021 continue; 1022 } 1023 1024 i++; 1025 int start = i; 1026 while (i < input.Length && input [i] != ')') 1027 i++; 1028 1029 string tag = input.Substring (start, i - start); 1030 1031 string tagValue; 1032 if (variables.TryGetValue (tag, out tagValue)) 1033 result.Append (tagValue); 1034 else { 1035 result.Append ('$'); 1036 i = start - 1; 1037 } 1038 } else { 1039 result.Append (input [i]); 1040 } 1041 i++; 1042 } 1043 return result.ToString (); 1044 } 1045 GetBool(string s, bool defval)1046 static bool GetBool (string s, bool defval) 1047 { 1048 if (s.Length == 0) 1049 return defval; 1050 else 1051 return s == "true" || s == "yes"; 1052 } 1053 ReadBinary(FileDatabase fdb, string configFile)1054 internal static AddinDescription ReadBinary (FileDatabase fdb, string configFile) 1055 { 1056 AddinDescription description = (AddinDescription) fdb.ReadSharedObject (configFile, typeMap); 1057 if (description != null) { 1058 description.FileName = configFile; 1059 description.canWrite = !fdb.IgnoreDescriptionData; 1060 } 1061 return description; 1062 } 1063 SaveBinary(FileDatabase fdb, string file)1064 internal void SaveBinary (FileDatabase fdb, string file) 1065 { 1066 configFile = file; 1067 SaveBinary (fdb); 1068 } 1069 SaveBinary(FileDatabase fdb)1070 internal void SaveBinary (FileDatabase fdb) 1071 { 1072 if (!canWrite) 1073 throw new InvalidOperationException ("Can't write incomplete description."); 1074 fdb.WriteSharedObject (AddinFile, FileName, typeMap, this); 1075 // BinaryXmlReader.DumpFile (configFile); 1076 } 1077 1078 /// <summary> 1079 /// Verify this instance. 1080 /// </summary> 1081 /// <remarks> 1082 /// This method checks all the definitions in the description and returns a list of errors. 1083 /// If the returned list is empty, it means that the description is valid. 1084 /// </remarks> Verify()1085 public StringCollection Verify () 1086 { 1087 return Verify (new AddinFileSystemExtension ()); 1088 } 1089 Verify(AddinFileSystemExtension fs)1090 internal StringCollection Verify (AddinFileSystemExtension fs) 1091 { 1092 StringCollection errors = new StringCollection (); 1093 1094 if (IsRoot) { 1095 if (OptionalModules.Count > 0) 1096 errors.Add ("Root add-in hosts can't have optional modules."); 1097 } 1098 1099 if (AddinId.Length == 0 || Version.Length == 0) { 1100 if (ExtensionPoints.Count > 0) 1101 errors.Add ("Add-ins which define new extension points must have an Id and Version."); 1102 } 1103 1104 MainModule.Verify ("", errors); 1105 OptionalModules.Verify ("", errors); 1106 ExtensionNodeSets.Verify ("", errors); 1107 ExtensionPoints.Verify ("", errors); 1108 ConditionTypes.Verify ("", errors); 1109 1110 foreach (ExtensionNodeSet nset in ExtensionNodeSets) { 1111 if (nset.Id.Length == 0) 1112 errors.Add ("Attribute 'id' can't be empty for global node sets."); 1113 } 1114 1115 string bp = null; 1116 if (BasePath.Length > 0) 1117 bp = BasePath; 1118 else if (sourceAddinFile != null && sourceAddinFile.Length > 0) 1119 bp = Path.GetDirectoryName (AddinFile); 1120 else if (configFile != null && configFile.Length > 0) 1121 bp = Path.GetDirectoryName (configFile); 1122 1123 if (bp != null) { 1124 foreach (string file in AllFiles) { 1125 string asmFile = Path.Combine (bp, file); 1126 if (!fs.FileExists (asmFile)) 1127 errors.Add ("The file '" + asmFile + "' referenced in the manifest could not be found."); 1128 } 1129 } 1130 1131 if (localizer != null && localizer.GetAttribute ("type").Length == 0) { 1132 errors.Add ("The attribute 'type' in the Location element is required."); 1133 } 1134 1135 // Ensure that there are no duplicated properties 1136 1137 if (properties != null) { 1138 HashSet<string> props = new HashSet<string> (); 1139 foreach (var prop in properties) { 1140 if (!props.Add (prop.Name + " " + prop.Locale)) 1141 errors.Add (string.Format ("Property {0} specified more than once", prop.Name + (prop.Locale != null ? " (" + prop.Locale + ")" : ""))); 1142 } 1143 } 1144 1145 return errors; 1146 } 1147 SetExtensionsAddinId(string addinId)1148 internal void SetExtensionsAddinId (string addinId) 1149 { 1150 foreach (ExtensionPoint ep in ExtensionPoints) 1151 ep.SetExtensionsAddinId (addinId); 1152 1153 foreach (ExtensionNodeSet ns in ExtensionNodeSets) 1154 ns.SetExtensionsAddinId (addinId); 1155 } 1156 UnmergeExternalData(Hashtable addins)1157 internal void UnmergeExternalData (Hashtable addins) 1158 { 1159 // Removes extension types and extension sets coming from other add-ins. 1160 foreach (ExtensionPoint ep in ExtensionPoints) 1161 ep.UnmergeExternalData (AddinId, addins); 1162 1163 foreach (ExtensionNodeSet ns in ExtensionNodeSets) 1164 ns.UnmergeExternalData (AddinId, addins); 1165 } 1166 MergeExternalData(AddinDescription other)1167 internal void MergeExternalData (AddinDescription other) 1168 { 1169 // Removes extension types and extension sets coming from other add-ins. 1170 foreach (ExtensionPoint ep in other.ExtensionPoints) { 1171 ExtensionPoint tep = ExtensionPoints [ep.Path]; 1172 if (tep != null) 1173 tep.MergeWith (AddinId, ep); 1174 } 1175 1176 foreach (ExtensionNodeSet ns in other.ExtensionNodeSets) { 1177 ExtensionNodeSet tns = ExtensionNodeSets [ns.Id]; 1178 if (tns != null) 1179 tns.MergeWith (AddinId, ns); 1180 } 1181 } 1182 1183 internal bool IsExtensionModel { 1184 get { return RootElement.LocalName == "ExtensionModel"; } 1185 } 1186 Merge(AddinDescription desc1, AddinDescription desc2)1187 internal static AddinDescription Merge (AddinDescription desc1, AddinDescription desc2) 1188 { 1189 if (!desc2.IsExtensionModel) { 1190 AddinDescription tmp = desc1; 1191 desc1 = desc2; desc2 = tmp; 1192 } 1193 ((AddinPropertyCollectionImpl)desc1.Properties).AddRange (desc2.Properties); 1194 desc1.ExtensionPoints.AddRange (desc2.ExtensionPoints); 1195 desc1.ExtensionNodeSets.AddRange (desc2.ExtensionNodeSets); 1196 desc1.ConditionTypes.AddRange (desc2.ConditionTypes); 1197 desc1.OptionalModules.AddRange (desc2.OptionalModules); 1198 foreach (string s in desc2.MainModule.Assemblies) 1199 desc1.MainModule.Assemblies.Add (s); 1200 foreach (string s in desc2.MainModule.DataFiles) 1201 desc1.MainModule.DataFiles.Add (s); 1202 desc1.MainModule.MergeWith (desc2.MainModule); 1203 return desc1; 1204 } 1205 IBinaryXmlElement.Write(BinaryXmlWriter writer)1206 void IBinaryXmlElement.Write (BinaryXmlWriter writer) 1207 { 1208 TransferCoreProperties (true); 1209 writer.WriteValue ("id", ParseString (id)); 1210 writer.WriteValue ("ns", ParseString (ns)); 1211 writer.WriteValue ("isroot", isroot); 1212 writer.WriteValue ("name", ParseString (name)); 1213 writer.WriteValue ("version", ParseString (version)); 1214 writer.WriteValue ("compatVersion", ParseString (compatVersion)); 1215 writer.WriteValue ("hasUserId", hasUserId); 1216 writer.WriteValue ("author", ParseString (author)); 1217 writer.WriteValue ("url", ParseString (url)); 1218 writer.WriteValue ("copyright", ParseString (copyright)); 1219 writer.WriteValue ("description", ParseString (description)); 1220 writer.WriteValue ("category", ParseString (category)); 1221 writer.WriteValue ("basePath", basePath); 1222 writer.WriteValue ("sourceAddinFile", sourceAddinFile); 1223 writer.WriteValue ("defaultEnabled", defaultEnabled); 1224 writer.WriteValue ("domain", domain); 1225 writer.WriteValue ("MainModule", MainModule); 1226 writer.WriteValue ("OptionalModules", OptionalModules); 1227 writer.WriteValue ("NodeSets", ExtensionNodeSets); 1228 writer.WriteValue ("ExtensionPoints", ExtensionPoints); 1229 writer.WriteValue ("ConditionTypes", ConditionTypes); 1230 writer.WriteValue ("FilesInfo", fileInfo); 1231 writer.WriteValue ("Localizer", localizer); 1232 writer.WriteValue ("flags", (int)flags); 1233 writer.WriteValue ("Properties", properties); 1234 } 1235 IBinaryXmlElement.Read(BinaryXmlReader reader)1236 void IBinaryXmlElement.Read (BinaryXmlReader reader) 1237 { 1238 id = reader.ReadStringValue ("id"); 1239 ns = reader.ReadStringValue ("ns"); 1240 isroot = reader.ReadBooleanValue ("isroot"); 1241 name = reader.ReadStringValue ("name"); 1242 version = reader.ReadStringValue ("version"); 1243 compatVersion = reader.ReadStringValue ("compatVersion"); 1244 hasUserId = reader.ReadBooleanValue ("hasUserId"); 1245 author = reader.ReadStringValue ("author"); 1246 url = reader.ReadStringValue ("url"); 1247 copyright = reader.ReadStringValue ("copyright"); 1248 description = reader.ReadStringValue ("description"); 1249 category = reader.ReadStringValue ("category"); 1250 basePath = reader.ReadStringValue ("basePath"); 1251 sourceAddinFile = reader.ReadStringValue ("sourceAddinFile"); 1252 defaultEnabled = reader.ReadBooleanValue ("defaultEnabled"); 1253 domain = reader.ReadStringValue ("domain"); 1254 mainModule = (ModuleDescription) reader.ReadValue ("MainModule"); 1255 optionalModules = (ModuleCollection) reader.ReadValue ("OptionalModules", new ModuleCollection (this)); 1256 nodeSets = (ExtensionNodeSetCollection) reader.ReadValue ("NodeSets", new ExtensionNodeSetCollection (this)); 1257 extensionPoints = (ExtensionPointCollection) reader.ReadValue ("ExtensionPoints", new ExtensionPointCollection (this)); 1258 conditionTypes = (ConditionTypeDescriptionCollection) reader.ReadValue ("ConditionTypes", new ConditionTypeDescriptionCollection (this)); 1259 fileInfo = (object[]) reader.ReadValue ("FilesInfo", null); 1260 localizer = (ExtensionNodeDescription) reader.ReadValue ("Localizer"); 1261 flags = (AddinFlags) reader.ReadInt32Value ("flags"); 1262 properties = (AddinPropertyCollectionImpl) reader.ReadValue ("Properties", new AddinPropertyCollectionImpl (this)); 1263 1264 if (mainModule != null) 1265 mainModule.SetParent (this); 1266 } 1267 } 1268 1269 class AddinFileInfo: IBinaryXmlElement 1270 { 1271 string fileName; 1272 DateTime timestamp; 1273 1274 public string FileName { 1275 get { 1276 return fileName; 1277 } 1278 set { 1279 fileName = value; 1280 } 1281 } 1282 1283 public System.DateTime Timestamp { 1284 get { 1285 return timestamp; 1286 } 1287 set { 1288 timestamp = value; 1289 } 1290 } 1291 Read(BinaryXmlReader reader)1292 public void Read (BinaryXmlReader reader) 1293 { 1294 fileName = reader.ReadStringValue ("fileName"); 1295 timestamp = reader.ReadDateTimeValue ("timestamp"); 1296 } 1297 Write(BinaryXmlWriter writer)1298 public void Write (BinaryXmlWriter writer) 1299 { 1300 writer.WriteValue ("fileName", fileName); 1301 writer.WriteValue ("timestamp", timestamp); 1302 } 1303 } 1304 } 1305