JD2022-TU1/main/tools/framework/JD.TeaBoxAddinSystem/Addins/Management/AddinXmlSchemaGenerator.cs

1291 lines
53 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using JD.Kernel.Classes;
using JD.Kernel.StaticHelpers;
using JD.LogService;
using JD.Serializers.Xml;
using JD.TeaBoxAddinSystem.Addins.Attributes;
using JD.TeaBoxAddinSystem.Addins.Description;
using JD.TeaBoxAddinSystem.Addins.Description.Conditions;
namespace JD.TeaBoxAddinSystem.Addins.Management
{
/// <summary>
/// Generates a schema from an addin repository.
/// </summary>
public class AddinXmlSchemaGenerator
{
#region Constants
private const string SchemaNamespace = "http://Addin";
private const string AbstractNodeType = "AbstractNode";
private const string AbstractExtensionNodeType = "AbstractExtensionNode";
private const string ExtensionNodeType = "ExtensionNode";
private const string AbstractExtensionTypeType = "AbstractExtensionType";
private const string DescriptionType = "Description";
private const string ImportResourceType = "ImportResource";
private const string ComplexConditionType = "ComplexCondition";
private const string ConditionalOperatorGroup = "ConditionalOperatorGroup";
private const string SimpleConditionGroup = "SimpleConditionGroup";
private const string ConditionGroup = "ConditionGroup";
private const string NodeOrConditionGroup = "NodeOrConditionGroup";
private const string TargetAttributeGroup = "TargetAttributeGroup";
private const string ExtensionTypePrefix = "ExtensionType.";
private const string ConditionTypePrefix = "ConditionType.";
#endregion
#region Variables
private readonly XmlDocument m_factoryDocument = new XmlDocument();
private readonly AddinRepository m_repository;
private readonly ILogger m_logger;
private readonly Dictionary<string, Addin> m_addins = new Dictionary<string, Addin>();
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
/// <param name="repository"></param>
/// <param name="logger"></param>
public AddinXmlSchemaGenerator(AddinRepository repository, ILogger logger)
{
Throw.IfNull(repository, "repository");
m_repository = repository;
m_logger = logger;
}
#endregion
#region Methods
#region Generation
/// <summary>
/// Generates a new schema.
/// </summary>
/// <returns></returns>
public XmlSchema Generate()
{
m_addins.Clear();
foreach (Addin addin in m_repository.Addins)
{
m_addins.Add(addin.FullId, addin);
}
CollectTypes();
XmlSchema addinSchema = new XmlSchema();
addinSchema.TargetNamespace = SchemaNamespace;
addinSchema.ElementFormDefault = XmlSchemaForm.Qualified;
addinSchema.Namespaces.Add("mstns", SchemaNamespace);
addinSchema.Namespaces.Add(string.Empty, SchemaNamespace);
addinSchema.Items.Add(CreateRootElement());
CreateComplexTypes(addinSchema);
CreateDynamicTypes(addinSchema);
return addinSchema;
}
/// <summary>
/// Generates a new schema and saves it to the given <paramref name="filename"/>.
/// </summary>
/// <param name="filename"></param>
public void GenerateAndSaveTo(string filename)
{
Throw.IfNull(filename, "filename");
XmlSchema schema = Generate();
using (MemoryStream memoryStream = new MemoryStream())
{
schema.Write(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
// Add a comment telling people to not temper directly with the file.
XmlDocument doc = new XmlDocument();
doc.Load(memoryStream);
XmlComment comment = doc.CreateComment("THIS SCHEMA IS AUTOGENERATED FROM THE CURRENT ADDIN REPOSITORY. DO NOT MODIFY THIS FILE DIRECTLY.");
doc.InsertBefore(comment, doc.DocumentElement);
using (var stream = System.IO.File.OpenWrite(filename))
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(stream, settings))
{
doc.WriteTo(writer);
}
}
}
}
private void CreateComplexTypes(XmlSchema schema)
{
schema.Items.Add(CreateImportResourceType());
schema.Items.Add(CreateDescriptionNodeType());
schema.Items.Add(CreateAbstractNodeType());
schema.Items.Add(CreateAbstractExtensionNodeType());
schema.Items.Add(CreateExtensionNodeType());
schema.Items.Add(CreateAbstractExtensionTypeType());
schema.Items.Add(CreateSimpleConditionGroup());
schema.Items.Add(CreateConditionGroup());
schema.Items.Add(CreateNodeOrConditionGroup());
CreateConditionalOperatorTypes(schema);
schema.Items.Add(CreateComplexConditionType());
if (m_conditionalTargets.Count > 0)
{
schema.Items.Add(CreateTargetAttributeGroup());
}
}
private XmlSchemaAttributeGroup CreateTargetAttributeGroup()
{
XmlSchemaAttributeGroup group = new XmlSchemaAttributeGroup();
group.Name = TargetAttributeGroup;
XmlSchemaAttribute attribute = CreateAttribute(AddinXmlSerializer.TargetAttribute, "Target on which to apply the condition", true, null);
XmlSchemaSimpleType simpleType = new XmlSchemaSimpleType();
XmlSchemaSimpleTypeRestriction restriction = new XmlSchemaSimpleTypeRestriction();
restriction.BaseTypeName = GetSimpleTypeQualifiedName(typeof(string));
foreach (KeyValuePair<string, string> conditionalTarget in m_conditionalTargets)
{
XmlSchemaEnumerationFacet enumFacet = new XmlSchemaEnumerationFacet();
enumFacet.Value = conditionalTarget.Key;
if (!string.IsNullOrEmpty(conditionalTarget.Value))
{
enumFacet.Annotation = CreateAnnotation(conditionalTarget.Value);
}
restriction.Facets.Add(enumFacet);
}
simpleType.Content = restriction;
attribute.SchemaType = simpleType;
group.Attributes.Add(attribute);
return group;
}
private XmlSchemaGroup CreateNodeOrConditionGroup()
{
XmlSchemaGroup group = new XmlSchemaGroup();
group.Name = NodeOrConditionGroup;
XmlSchemaChoice choice = new XmlSchemaChoice();
XmlSchemaGroupRef conditionRef = new XmlSchemaGroupRef();
conditionRef.RefName = new XmlQualifiedName(ConditionGroup);
conditionRef.MinOccurs = 0;
conditionRef.MaxOccurs = decimal.MaxValue;
choice.Items.Add(conditionRef);
foreach (ExtensionType extensionType in m_extensionTypeToType.Keys)
{
XmlSchemaElement element = CreateComplexElementReference(extensionType.Name, ExtensionTypePrefix + extensionType.Name, extensionType.Description);
element.MinOccurs = 0;
element.MaxOccurs = decimal.MaxValue;
choice.Items.Add(element);
}
group.Particle = choice;
return group;
}
private XmlSchemaGroup CreateConditionGroup()
{
XmlSchemaGroup group = new XmlSchemaGroup();
group.Name = ConditionGroup;
XmlSchemaChoice choice = new XmlSchemaChoice();
// Add all condition types
foreach (ConditionType type in m_conditionTypeToType.Keys)
{
XmlSchemaElement conditionElement = CreateComplexElement(AddinXmlSerializer.ConditionPrefix + "." + type.Name, type.Description);
XmlSchemaComplexType conditionComplexType = CreateComplexType(null);
XmlSchemaComplexContent contentModel = new XmlSchemaComplexContent();
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
extension.BaseTypeName = new XmlQualifiedName(ConditionTypePrefix + type.Name);
contentModel.Content = extension;
conditionComplexType.ContentModel = contentModel;
// Add target attribute
if (m_conditionalTargets.Count > 0)
{
extension.Attributes.Add(CreateTargetAttribute());
}
// Add children
XmlSchemaGroupRef groupRef = new XmlSchemaGroupRef();
groupRef.RefName = new XmlQualifiedName(NodeOrConditionGroup);
XmlSchemaChoice conditionChoice = new XmlSchemaChoice();
conditionChoice.MinOccurs = 0;
conditionChoice.MaxOccurs = decimal.MaxValue;
conditionChoice.Items.Add(groupRef);
extension.Particle = conditionChoice;
conditionElement.SchemaType = conditionComplexType;
choice.Items.Add(conditionElement);
}
// Add complex condition
XmlSchemaElement complexConditionElement = CreateComplexElementReference(AddinXmlSerializer.ConditionPrefix, ComplexConditionType, "Implements a complex condition.");
complexConditionElement.MinOccurs = 0;
complexConditionElement.MaxOccurs = decimal.MaxValue;
choice.Items.Add(complexConditionElement);
group.Particle = choice;
return group;
}
private XmlSchemaGroup CreateSimpleConditionGroup()
{
XmlSchemaGroup group = new XmlSchemaGroup();
group.Name = SimpleConditionGroup;
// Add all condition types
XmlSchemaChoice choice = new XmlSchemaChoice();
foreach (ConditionType type in m_conditionTypeToType.Keys)
{
XmlSchemaElement conditionElement = CreateComplexElementReference(AddinXmlSerializer.ConditionPrefix + "." + type.Name, ConditionTypePrefix + type.Name, type.Description);
choice.Items.Add(conditionElement);
}
group.Particle = choice;
return group;
}
private XmlSchemaComplexType CreateComplexConditionType()
{
XmlSchemaComplexType complexConditionType = CreateComplexType(ComplexConditionType);
if (m_conditionalTargets.Count > 0)
{
complexConditionType.Attributes.Add(CreateTargetAttribute());
}
XmlSchemaSequence sequence = new XmlSchemaSequence();
// First child must be conditional operator
XmlSchemaGroupRef groupRef = new XmlSchemaGroupRef();
groupRef.MinOccurs = 1;
groupRef.MaxOccurs = 1;
groupRef.RefName = new XmlQualifiedName(ConditionalOperatorGroup);
sequence.Items.Add(groupRef);
// Other children can be anything
groupRef = new XmlSchemaGroupRef();
groupRef.MinOccurs = 1;
groupRef.MaxOccurs = decimal.MaxValue;
groupRef.RefName = new XmlQualifiedName(NodeOrConditionGroup);
sequence.Items.Add(groupRef);
complexConditionType.Particle = sequence;
return complexConditionType;
}
private void CreateConditionalOperatorTypes(XmlSchema schema)
{
System.Diagnostics.Debug.Assert(schema != null);
List<XmlSchemaComplexType> allConditionalOperators = new List<XmlSchemaComplexType>();
// Not
XmlSchemaComplexType notComplexType = CreateConditionalOperatorComplexType(AddinXmlSerializer.NotNode, true, "Implements a NOT operator.");
schema.Items.Add(notComplexType);
allConditionalOperators.Add(notComplexType);
// Or
XmlSchemaComplexType orComplexType = CreateConditionalOperatorComplexType(AddinXmlSerializer.OrNode, false, "Implements an OR operator.");
schema.Items.Add(orComplexType);
allConditionalOperators.Add(orComplexType);
// And
XmlSchemaComplexType andComplexType = CreateConditionalOperatorComplexType(AddinXmlSerializer.AndNode, false, "Implements an AND operator.");
schema.Items.Add(andComplexType);
allConditionalOperators.Add(andComplexType);
// Xor
XmlSchemaComplexType xorComplexType = CreateConditionalOperatorComplexType(AddinXmlSerializer.XorNode, false, "Implements a XOR operator.");
schema.Items.Add(xorComplexType);
allConditionalOperators.Add(xorComplexType);
// True
XmlSchemaComplexType trueComplexType = CreateComplexType(AddinXmlSerializer.TrueNode);
trueComplexType.Annotation = CreateAnnotation("A condition that always evaluate to true.");
schema.Items.Add(trueComplexType);
allConditionalOperators.Add(trueComplexType);
// False
XmlSchemaComplexType falseComplexType = CreateComplexType(AddinXmlSerializer.FalseNode);
falseComplexType.Annotation = CreateAnnotation("A condition that always evaluate to false.");
schema.Items.Add(falseComplexType);
allConditionalOperators.Add(falseComplexType);
XmlSchemaGroup group = new XmlSchemaGroup();
group.Name = ConditionalOperatorGroup;
XmlSchemaChoice choice = new XmlSchemaChoice();
foreach (XmlSchemaComplexType complexType in allConditionalOperators)
{
choice.Items.Add(CreateComplexElementReference(complexType.Name, complexType.Name, null));
}
group.Particle = choice;
schema.Items.Add(group);
}
private XmlSchemaComplexType CreateConditionalOperatorComplexType(string name, bool restricted, string description)
{
XmlSchemaComplexType complexType = CreateComplexType(name);
XmlSchemaChoice choice = new XmlSchemaChoice();
choice.Items.Add(CreateConditionalOperatorGroupRef());
XmlSchemaGroupRef groupRef = new XmlSchemaGroupRef();
groupRef.RefName = new XmlQualifiedName(SimpleConditionGroup);
choice.Items.Add(groupRef);
complexType.Particle = choice;
complexType.Annotation = CreateAnnotation(description);
if (restricted)
{
complexType.Particle.MinOccurs = 1;
complexType.Particle.MaxOccurs = 1;
}
else
{
complexType.Particle.MinOccurs = 1;
complexType.Particle.MaxOccurs = decimal.MaxValue;
}
return complexType;
}
private XmlSchemaGroupRef CreateConditionalOperatorGroupRef()
{
XmlSchemaGroupRef groupRef = new XmlSchemaGroupRef();
groupRef.RefName = new XmlQualifiedName(ConditionalOperatorGroup);
return groupRef;
}
private XmlSchemaAttributeGroupRef CreateTargetAttribute()
{
XmlSchemaAttributeGroupRef groupRef = new XmlSchemaAttributeGroupRef();
groupRef.RefName = new XmlQualifiedName(TargetAttributeGroup);
return groupRef;
}
private XmlSchemaComplexType CreateImportResourceType()
{
XmlSchemaComplexType importResourceType = CreateComplexType(ImportResourceType);
importResourceType.Attributes.Add(CreateAttribute(AddinXmlSerializer.ResourceAttribute, "External resource file used by the current addin/module/assembly to get localized resources", true));
return importResourceType;
}
private XmlSchemaComplexType CreateDescriptionNodeType()
{
XmlSchemaComplexType descriptionNodeType = CreateComplexType(DescriptionType);
XmlSchemaSimpleContent contentModel = new XmlSchemaSimpleContent();
XmlSchemaSimpleContentExtension extension = new XmlSchemaSimpleContentExtension();
extension.BaseTypeName = XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.String).QualifiedName;
contentModel.Content = extension;
descriptionNodeType.ContentModel = contentModel;
return descriptionNodeType;
}
private XmlSchemaComplexType CreateAbstractNodeType()
{
// <xs:complex-type name="AbstractNode">
XmlSchemaComplexType abstractNodeType = CreateComplexType(AbstractNodeType);
// Attributes
abstractNodeType.Attributes.Add(CreateAttribute(AddinXmlSerializer.DescriptionAttribute, "Description of the node.", true));
// Child elements
XmlSchemaElement descriptionElement = CreateComplexElementReference(AddinXmlSerializer.DescriptionNode, DescriptionType, "Description of the node.");
descriptionElement.MinOccurs = 0;
descriptionElement.MaxOccurs = 1;
abstractNodeType.Particle = CreateParticle(descriptionElement);
abstractNodeType.Particle.MinOccurs = 0;
abstractNodeType.Particle.MaxOccurs = 1;
return abstractNodeType;
}
private XmlSchemaComplexType CreateAbstractExtensionTypeType()
{
XmlSchemaComplexType abstractExtensionType = CreateComplexType(AbstractExtensionTypeType);
// Attributes
abstractExtensionType.Attributes.Add(CreateAttribute(ExtensionNodeDescription.IdAttributeName, "Id of the node.", true));
abstractExtensionType.Attributes.Add(CreateAttribute(ExtensionNodeDescription.InsertBeforeAttributeName, "Id of nodes that need to be placed after this node, separated by commas", true));
abstractExtensionType.Attributes.Add(CreateAttribute(ExtensionNodeDescription.InsertAfterAttributeName, "Id of nodes that need to be placed before this node, separated by commas", true));
return abstractExtensionType;
}
private XmlSchemaComplexType CreateAbstractExtensionNodeType()
{
XmlSchemaComplexType abstractExtensionNodeType = CreateComplexType(AbstractExtensionNodeType);
XmlSchemaComplexContent contentModel = new XmlSchemaComplexContent();
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
extension.BaseTypeName = new XmlQualifiedName(AbstractNodeType);
contentModel.Content = extension;
abstractExtensionNodeType.ContentModel = contentModel;
// Child elements
XmlSchemaElement nodeElement = CreateComplexElementReference(AddinXmlSerializer.ExtensionNodeNode, ExtensionNodeType, "Defines a type of extension node valid in this context.");
nodeElement.MinOccurs = 0;
nodeElement.MaxOccurs = decimal.MaxValue;
XmlSchemaElement nodeSetElement = CreateComplexElement(AddinXmlSerializer.ExtensionNodeSetNode, "References an existing extension node set.");
XmlSchemaComplexType nodeSetElementType = new XmlSchemaComplexType();
nodeSetElementType.Attributes.Add(CreateAttribute(AddinXmlSerializer.NameAttribute, "Name of the extension node set.", false));
nodeSetElement.SchemaType = nodeSetElementType;
extension.Particle = CreateParticle(nodeElement, nodeSetElement);
return abstractExtensionNodeType;
}
private XmlSchemaComplexType CreateExtensionNodeType()
{
// <xs:complex-type name="ExtensionNode">
XmlSchemaComplexType extensionNodeType = CreateComplexType(ExtensionNodeType);
XmlSchemaComplexContent contentModel = new XmlSchemaComplexContent();
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
extension.BaseTypeName = new XmlQualifiedName(AbstractExtensionNodeType);
contentModel.Content = extension;
extensionNodeType.ContentModel = contentModel;
// Attributes
extension.Attributes.Add(CreateAttribute(AddinXmlSerializer.NameAttribute, "Name of the extension node. This name is used to identify the extension node in the extensions where it is used.", false));
extension.Attributes.Add(CreateAttribute(AddinXmlSerializer.TypeAttribute, "CLR type corresponding to the extension node. It must derive from " + typeof(ExtensionNode).FullName + " and it must be fully qualified.", true));
extension.Attributes.Add(CreateAttribute(AddinXmlSerializer.ObjectTypeAttribute, "Base class of objects that this type of node will produce. This is only used when type is not specified.", true));
return extensionNodeType;
}
private XmlSchemaElement CreateModuleElement()
{
XmlSchemaElement moduleElement = CreateComplexElement(AddinXmlSerializer.ModuleNode, "Describes an optional module in the addin.");
XmlSchemaComplexType moduleType = CreateComplexType(null);
// Child elements
moduleType.Particle = CreateParticle(CreateBaseModuleElements().ToArray());
moduleElement.SchemaType = moduleType;
return moduleElement;
}
private List<XmlSchemaElement> CreateBaseModuleElements()
{
List<XmlSchemaElement> elements = new List<XmlSchemaElement>();
// Files
XmlSchemaElement filesElement = CreateFilesElement();
filesElement.MinOccurs = 0;
filesElement.MaxOccurs = decimal.MaxValue;
elements.Add(filesElement);
// Dependencies
XmlSchemaElement dependenciesElement = CreateDependenciesElement();
dependenciesElement.MinOccurs = 0;
dependenciesElement.MaxOccurs = decimal.MaxValue;
elements.Add(dependenciesElement);
// Extension
XmlSchemaElement extensionElement = CreateExtensionElement();
extensionElement.MinOccurs = 0;
extensionElement.MaxOccurs = decimal.MaxValue;
elements.Add(extensionElement);
return elements;
}
private XmlSchemaElement CreateExtensionElement()
{
XmlSchemaElement extensionElement = CreateComplexElement(AddinXmlSerializer.ExtensionNode, "Defines an extension to an existing extension point.");
XmlSchemaComplexType extensionElementType = CreateComplexType(null);
extensionElementType.Attributes.Add(CreateAttribute(AddinXmlSerializer.PathAttribute, "Path of the extension point to extend.", false));
// Child elements
XmlSchemaGroupRef groupRef = new XmlSchemaGroupRef();
groupRef.RefName = new XmlQualifiedName(NodeOrConditionGroup);
XmlSchemaChoice choice = new XmlSchemaChoice();
choice.MinOccurs = 0;
choice.MaxOccurs = decimal.MaxValue;
choice.Items.Add(groupRef);
extensionElementType.Particle = choice;
extensionElement.SchemaType = extensionElementType;
return extensionElement;
}
private XmlSchemaElement CreateFilesElement()
{
XmlSchemaElement filesElement = CreateComplexElement(AddinXmlSerializer.FilesNode, "Describes the files used by the current addin/module.");
XmlSchemaComplexType filesType = CreateComplexType(null);
XmlSchemaElement importElement = CreateComplexElement(AddinXmlSerializer.ImportNode, "Describes a file used by the current addin/module.");
XmlSchemaComplexType importType = CreateComplexType(null);
XmlSchemaComplexContent contentModel = new XmlSchemaComplexContent();
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
extension.BaseTypeName = new XmlQualifiedName(ImportResourceType);
contentModel.Content = extension;
importType.ContentModel = contentModel;
// Import attributes (resource attribute is already in base type)
extension.Attributes.Add(CreateAttribute(AddinXmlSerializer.AssemblyAttribute, "Assembly used by the current addin/module to instantiate types", true));
extension.Attributes.Add(CreateAttribute(AddinXmlSerializer.FileAttribute, "File used by the current addin/module", true));
XmlSchemaElement childImportNode = CreateComplexElementReference(AddinXmlSerializer.ImportNode, ImportResourceType, "Describes a file used by the current addin/module/assembly");
childImportNode.MinOccurs = 0;
childImportNode.MaxOccurs = decimal.MaxValue;
extension.Particle = CreateParticle(childImportNode);
importElement.SchemaType = importType;
importElement.MinOccurs = 0;
importElement.MaxOccurs = decimal.MaxValue;
filesType.Particle = CreateParticle(importElement);
filesElement.SchemaType = filesType;
return filesElement;
}
private XmlSchemaElement CreateDependenciesElement()
{
XmlSchemaElement dependenciesElement = CreateComplexElement(AddinXmlSerializer.DependenciesNode, "Describes the dependencies of the current addin/module. An addin/module can only be loaded if all its dependencies are also loaded.");
XmlSchemaComplexType dependenciesType = CreateComplexType(null);
XmlSchemaElement addinDependencyElement = CreateComplexElement(AddinXmlSerializer.AddinNode, "A dependency on another addin.");
XmlSchemaComplexType addinDependencyType = CreateComplexType(null);
addinDependencyType.Attributes.Add(CreateAttribute(AddinXmlSerializer.IdAttribute, "Fully qualified id of the addin.", false));
addinDependencyType.Attributes.Add(CreateAttribute(AddinXmlSerializer.VersionAttribute, "Version or version range of the addin. When specified, the dependency will refer to a specific version range of the addin.", true));
addinDependencyElement.SchemaType = addinDependencyType;
dependenciesType.Particle = CreateParticle(addinDependencyElement);
dependenciesElement.SchemaType = dependenciesType;
return dependenciesElement;
}
private XmlSchemaElement CreateRootElement()
{
XmlSchemaElement rootElement = CreateComplexElement(XmlSerializerWriter.RootElement, "Root element");
XmlSchemaComplexType rootType = CreateComplexType(null);
XmlSchemaElement addinElement = CreateAddinElement();
rootType.Particle = CreateParticle(addinElement);
rootElement.SchemaType = rootType;
return rootElement;
}
private XmlSchemaElement CreateAddinElement()
{
// Addin element
XmlSchemaElement addinElement = CreateComplexElement(AddinXmlSerializer.AddinNode, "Defines an addin.");
XmlSchemaComplexType addinType = CreateComplexType(null);
// Attributes
addinType.Attributes.Add(CreateAttribute(AddinXmlSerializer.IdAttribute, "Unique id of the addin.", false));
addinType.Attributes.Add(CreateAttribute(AddinXmlSerializer.NameAttribute, "Friendly name of the addin. If not specified, the name of the addin will be its id.", true));
addinType.Attributes.Add(CreateAttribute(AddinXmlSerializer.VersionAttribute, "Version of the addin.", true));
addinType.Attributes.Add(CreateAttribute(AddinXmlSerializer.AuthorAttribute, "Author of the addin.", true));
addinType.Attributes.Add(CreateAttribute(AddinXmlSerializer.UrlAttribute, "Url related to the addin.", true));
addinType.Attributes.Add(CreateAttribute(AddinXmlSerializer.DescriptionAttribute, "Description of the addin.", true));
addinType.Attributes.Add(CreateAttribute(AddinXmlSerializer.CategoryAttribute, "Category of the addin.", true));
// Child elements
List<XmlSchemaElement> childElements = CreateBaseModuleElements();
// ConditionNode
XmlSchemaElement conditionNodeElement = CreateConditionNodeElement();
childElements.Add(conditionNodeElement);
// Extension point
XmlSchemaElement extensionPointElement = CreateExtensionPointElement();
childElements.Add(extensionPointElement);
// Extension node set
XmlSchemaElement extensionNodeSetElement = CreateExtensionNodeSetElement();
childElements.Add(extensionNodeSetElement);
// Include
XmlSchemaElement includeElement = CreateIncludeElement();
childElements.Add(includeElement);
// Module
XmlSchemaElement moduleElement = CreateModuleElement();
childElements.Add(moduleElement);
addinType.Particle = CreateParticle(childElements.ToArray());
addinElement.SchemaType = addinType;
return addinElement;
}
private XmlSchemaElement CreateConditionNodeElement()
{
XmlSchemaElement conditionNodeElement = CreateComplexElement(AddinXmlSerializer.ConditionNodeNode, "Defines a new type of condition.");
XmlSchemaComplexType conditionNodeType = CreateComplexType(null);
XmlSchemaComplexContent contentModel = new XmlSchemaComplexContent();
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
extension.BaseTypeName = new XmlQualifiedName(AbstractNodeType);
contentModel.Content = extension;
conditionNodeType.ContentModel = contentModel;
extension.Attributes.Add(CreateAttribute(AddinXmlSerializer.NameAttribute, "Name of the condition node. This name is used to identify the node in the extensions where it is used.", false));
extension.Attributes.Add(CreateAttribute(AddinXmlSerializer.TypeAttribute, "CLR type corresponding to the condition node. It must derive from either " + typeof(ConditionNode).FullName + " or " + typeof(PolledConditionNode).FullName + " and it must be fully qualified.", false));
conditionNodeElement.SchemaType = conditionNodeType;
return conditionNodeElement;
}
private XmlSchemaElement CreateExtensionPointElement()
{
XmlSchemaElement extensionPointElement = CreateComplexElement(AddinXmlSerializer.ExtensionPointNode, "Defines a new extension point.");
XmlSchemaComplexType extensionPointType = CreateComplexType(null);
XmlSchemaComplexContent contentModel = new XmlSchemaComplexContent();
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
extension.BaseTypeName = new XmlQualifiedName(AbstractExtensionNodeType);
contentModel.Content = extension;
extensionPointType.ContentModel = contentModel;
extension.Attributes.Add(CreateAttribute(AddinXmlSerializer.PathAttribute, "Path of the extension point.", false));
extensionPointElement.SchemaType = extensionPointType;
return extensionPointElement;
}
private XmlSchemaElement CreateExtensionNodeSetElement()
{
XmlSchemaElement extensionNodeSetElement = CreateComplexElement(AddinXmlSerializer.ExtensionNodeSetNode, "Defines a new extension node set.");
XmlSchemaComplexType extensionNodeSetType = CreateComplexType(null);
XmlSchemaComplexContent contentModel = new XmlSchemaComplexContent();
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
extension.BaseTypeName = new XmlQualifiedName(AbstractExtensionNodeType);
contentModel.Content = extension;
extensionNodeSetType.ContentModel = contentModel;
extension.Attributes.Add(CreateAttribute(AddinXmlSerializer.NameAttribute, "Name of the extension node set.", false));
extensionNodeSetElement.SchemaType = extensionNodeSetType;
return extensionNodeSetElement;
}
private XmlSchemaElement CreateIncludeElement()
{
XmlSchemaElement includeElement = CreateComplexElement(AddinXmlSerializer.IncludeNode, "Includes a partial addin file.");
XmlSchemaComplexType includeType = CreateComplexType(null);
includeType.Attributes.Add(CreateAttribute(AddinXmlSerializer.FileAttribute, "Filename of the partial addin to include.", false));
includeElement.SchemaType = includeType;
return includeElement;
}
#endregion
#region Data Collecting
private readonly Dictionary<string, ExtensionTypeSet> m_extensionTypeSets = new Dictionary<string, ExtensionTypeSet>();
private readonly Dictionary<ExtensionType, Type> m_extensionTypeToType = new Dictionary<ExtensionType, Type>();
private readonly Dictionary<ConditionType, Type> m_conditionTypeToType = new Dictionary<ConditionType, Type>();
private readonly Dictionary<string, string> m_conditionalTargets = new Dictionary<string, string>();
private void CollectTypes()
{
m_extensionTypeToType.Clear();
m_conditionTypeToType.Clear();
m_conditionalTargets.Clear();
CollectExtensionTypeSets();
ProcessExtensionTypeSets();
ProcessConditionalTargets();
}
private void ProcessExtensionTypeSets()
{
List<string> typeSetNames = CollectTypesInAddins();
foreach (ExtensionTypeSet extensionTypeSet in m_extensionTypeSets.Values)
{
ProcessExtensionTypeSet(extensionTypeSet, typeSetNames);
}
}
private void ProcessConditionalTargets()
{
foreach (Type clrType in m_extensionTypeToType.Values)
{
if (clrType != null)
{
// Check for conditional target attributes
foreach (ValueMemberInfo memberInfo in ValueMemberInfo.GetValueMembers(clrType, true))
{
ConditionalTargetAttribute attribute = (ConditionalTargetAttribute)Attribute.GetCustomAttribute(memberInfo.InnerMemberInfo, typeof(ConditionalTargetAttribute), true);
if (attribute != null)
{
AddConditionalTarget(memberInfo, attribute);
}
}
}
}
}
private List<string> CollectTypesInAddins()
{
List<string> typeSetNames = new List<string>();
foreach (Addin addin in m_repository.Addins)
{
foreach (ExtensionPoint extensionPoint in addin.ExtensionPoints)
{
ProcessExtensionTypeSet(extensionPoint.ExtensionTypeSet, typeSetNames);
}
foreach (ConditionType type in addin.ConditionTypes)
{
ProcessConditionType(type);
}
}
return typeSetNames;
}
private void CollectExtensionTypeSets()
{
m_extensionTypeSets.Clear();
foreach (Addin addin in m_repository.Addins)
{
foreach (ExtensionTypeSet typeSet in addin.ExtensionTypeSets)
{
// Ignore duplicates, we assume that the addin data is is relatively ok already.
if (!m_extensionTypeSets.ContainsKey(typeSet.Name))
{
m_extensionTypeSets.Add(typeSet.Name, typeSet);
}
}
}
}
private void AddConditionalTarget(ValueMemberInfo memberInfo, ConditionalTargetAttribute attribute)
{
System.Diagnostics.Debug.Assert(memberInfo != null);
System.Diagnostics.Debug.Assert(attribute != null);
string name = string.IsNullOrEmpty(attribute.Name) ? memberInfo.Name : attribute.Name;
if (!m_conditionalTargets.ContainsKey(name))
{
m_conditionalTargets.Add(name, attribute.Description);
}
}
private void ProcessExtensionTypeSet(ExtensionTypeSet typeSet, IList<string> processedSets)
{
System.Diagnostics.Debug.Assert(typeSet != null);
System.Diagnostics.Debug.Assert(processedSets != null);
System.Diagnostics.Debug.Assert(!processedSets.IsReadOnly);
foreach (ExtensionType type in typeSet.ExtensionTypes)
{
ProcessExtensionType(type);
ProcessExtensionTypeSet(type.ExtensionTypeSet, processedSets);
}
foreach (string typeSetName in typeSet.ExtensionTypeSets)
{
if (!processedSets.Contains(typeSetName))
{
processedSets.Add(typeSetName);
ExtensionTypeSet inlineTypeSet;
if (m_extensionTypeSets.TryGetValue(typeSetName, out inlineTypeSet))
{
ProcessExtensionTypeSet(inlineTypeSet, processedSets);
}
}
}
}
private void ProcessExtensionType(ExtensionType type)
{
System.Diagnostics.Debug.Assert(type != null);
if (m_extensionTypeToType.ContainsKey(type))
{
return;
}
// Cannot have two elements with the same name in the xml schema, so we have to ignore types that have the same name :(
foreach (ExtensionType existingType in m_extensionTypeToType.Keys)
{
if (type.Name == existingType.Name)
{
return;
}
}
m_extensionTypeToType.Add(type, GetCLRType(type));
}
private void ProcessConditionType(ConditionType type)
{
System.Diagnostics.Debug.Assert(type != null);
if (!m_conditionTypeToType.ContainsKey(type))
{
m_conditionTypeToType.Add(type, GetType(type.Module, type.TypeName));
}
}
private Type GetCLRType(ExtensionType type)
{
if (string.IsNullOrEmpty(type.TypeName))
{
return typeof(TypeExtensionNode);
}
else
{
return GetType(type.Module, type.TypeName);
}
}
private Type GetType(Module module, string typeName)
{
// Look in module
Type type = module.GetType(typeName);
if (type != null)
{
return type;
}
// Look in dependencies
foreach (AddinReference reference in module.AllDependencies)
{
Addin addin;
if (m_addins.TryGetValue(reference.Name, out addin))
{
type = GetType(addin.MainModule, typeName);
if (type != null)
{
return type;
}
}
}
return null;
}
private void CreateDynamicTypes(XmlSchema schema)
{
System.Diagnostics.Debug.Assert(schema != null);
foreach (Addin addin in m_repository.Addins)
{
// Extension types
foreach (ExtensionPoint point in addin.ExtensionPoints)
{
foreach (XmlSchemaComplexType type in CreateDynamicTypesForExtensionTypeSet(point.ExtensionTypeSet))
{
schema.Items.Add(type);
}
}
foreach (ExtensionTypeSet typeSet in addin.ExtensionTypeSets)
{
foreach (XmlSchemaComplexType type in CreateDynamicTypesForExtensionTypeSet(typeSet))
{
schema.Items.Add(type);
}
}
// Condition types
foreach (ConditionType conditionType in addin.ConditionTypes)
{
XmlSchemaComplexType type = CreateDynamicTypeForConditionType(conditionType);
if (type != null)
{
schema.Items.Add(type);
}
}
}
}
private IEnumerable<XmlSchemaComplexType> CreateDynamicTypesForExtensionTypeSet(ExtensionTypeSet typeSet)
{
System.Diagnostics.Debug.Assert(typeSet != null);
foreach (ExtensionType type in typeSet.ExtensionTypes)
{
if (m_extensionTypeToType.ContainsKey(type))
{
XmlSchemaComplexType schemaType = CreateExtensionTypeElement(type);
if (schemaType != null)
{
yield return schemaType;
}
}
foreach (XmlSchemaComplexType childSchemaType in CreateDynamicTypesForExtensionTypeSet(type.ExtensionTypeSet))
{
yield return childSchemaType;
}
}
}
private XmlSchemaComplexType CreateExtensionTypeElement(ExtensionType type)
{
System.Diagnostics.Debug.Assert(type != null);
XmlSchemaComplexType complexType = CreateComplexType(ExtensionTypePrefix + type.Name);
XmlSchemaComplexContent contentModel = new XmlSchemaComplexContent();
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
extension.BaseTypeName = new XmlQualifiedName(AbstractExtensionTypeType);
contentModel.Content = extension;
complexType.ContentModel = contentModel;
// Attributes
System.Diagnostics.Debug.Assert(m_extensionTypeToType.ContainsKey(type));
Type clrType = m_extensionTypeToType[type];
if (clrType != null)
{
foreach (XmlSchemaAttribute attribute in CreateDynamicAttributes(clrType))
{
extension.Attributes.Add(attribute);
}
}
else
{
m_logger.LogWarning("Could not find type {0}, ignoring...", type.TypeName);
}
// Child elements
List<XmlSchemaObject> childElements = new List<XmlSchemaObject>();
foreach(ExtensionType childType in CollectChildTypes(type.ExtensionTypeSet))
{
XmlSchemaElement childElement = CreateComplexElementReference(childType.Name, ExtensionTypePrefix + childType.Name, childType.Description);
childElement.MinOccurs = 0;
childElement.MaxOccurs = decimal.MaxValue;
childElements.Add(childElement);
}
XmlSchemaGroupRef groupRef = new XmlSchemaGroupRef();
groupRef.RefName = new XmlQualifiedName(ConditionGroup);
childElements.Add(groupRef);
extension.Particle = CreateParticle(childElements.ToArray());
return complexType;
}
private XmlSchemaComplexType CreateDynamicTypeForConditionType(ConditionType type)
{
System.Diagnostics.Debug.Assert(type != null);
XmlSchemaComplexType complexType = CreateComplexType(ConditionTypePrefix + type.Name);
// Attributes
System.Diagnostics.Debug.Assert(m_conditionTypeToType.ContainsKey(type));
Type clrType = m_conditionTypeToType[type];
if (clrType != null)
{
foreach (XmlSchemaAttribute attribute in CreateDynamicAttributes(clrType))
{
complexType.Attributes.Add(attribute);
}
}
return complexType;
}
private IEnumerable<ExtensionType> CollectChildTypes(ExtensionTypeSet typeSet)
{
System.Diagnostics.Debug.Assert(typeSet != null);
foreach (ExtensionType type in typeSet.ExtensionTypes)
{
yield return type;
}
foreach (string setReference in typeSet.ExtensionTypeSets)
{
ExtensionTypeSet inlineSet;
if (m_extensionTypeSets.TryGetValue(setReference, out inlineSet))
{
foreach(ExtensionType type in CollectChildTypes(inlineSet))
{
yield return type;
}
}
}
}
private IEnumerable<XmlSchemaAttribute> CreateDynamicAttributes(Type clrType)
{
System.Diagnostics.Debug.Assert(clrType != null);
foreach (ValueMemberInfo memberInfo in ValueMemberInfo.GetValueMembers(clrType, true))
{
NodeAttributeAttribute attribute = (NodeAttributeAttribute)Attribute.GetCustomAttribute(memberInfo.InnerMemberInfo, typeof(NodeAttributeAttribute), true);
if (attribute != null)
{
yield return CreateDynamicAttribute(memberInfo, attribute);
}
}
}
private XmlSchemaAttribute CreateDynamicAttribute(ValueMemberInfo memberInfo, NodeAttributeAttribute attribute)
{
System.Diagnostics.Debug.Assert(memberInfo != null);
System.Diagnostics.Debug.Assert(attribute != null);
string name = string.IsNullOrEmpty(attribute.Name) ? memberInfo.Name : attribute.Name;
return CreateAttribute(name, attribute.Description, !attribute.Required, memberInfo.ValueType);
}
#endregion
#region Helper methods
private XmlSchemaChoice CreateParticle(params XmlSchemaObject[] elements)
{
XmlSchemaChoice choice = new XmlSchemaChoice();
foreach (XmlSchemaObject element in elements)
{
choice.Items.Add(element);
}
choice.MinOccurs = 0;
choice.MaxOccurs = decimal.MaxValue;
return choice;
}
private XmlSchemaElement CreateComplexElement(string elementName, string description)
{
XmlSchemaElement element = new XmlSchemaElement();
element.Name = elementName;
element.Annotation = CreateAnnotation(description);
return element;
}
private XmlSchemaElement CreateComplexElementReference(string elementName, string complexTypeReference, string description)
{
XmlSchemaElement element = new XmlSchemaElement();
element.Name = elementName;
if (!string.IsNullOrEmpty(description))
{
element.Annotation = CreateAnnotation(description);
}
element.SchemaTypeName = new XmlQualifiedName(complexTypeReference);
return element;
}
private XmlSchemaComplexType CreateComplexType(string name)
{
XmlSchemaComplexType complexType = new XmlSchemaComplexType();
complexType.Name = name;
return complexType;
}
private XmlSchemaAttribute CreateAttribute(string attributeName, string description, bool optional)
{
return CreateAttribute(attributeName, description, optional, typeof(string));
}
private XmlSchemaAttribute CreateAttribute(string attributeName, string description, bool optional, Type type)
{
if (optional && !string.IsNullOrEmpty(description))
{
description += " [Optional]";
}
XmlSchemaAttribute attribute = new XmlSchemaAttribute();
attribute.Name = attributeName;
attribute.Annotation = CreateAnnotation(description);
if (type != null)
{
if (type.IsEnum)
{
FillEnumAttribute(attribute, type);
}
else if (type == typeof(bool)) // Special case for booleans
{
FillBoolAttribute(attribute);
}
else
{
FillGenericAttribute(attribute, type);
}
}
attribute.Use = optional ? XmlSchemaUse.Optional : XmlSchemaUse.Required;
return attribute;
}
private static void FillGenericAttribute(XmlSchemaAttribute attribute, Type type)
{
XmlQualifiedName typeName = GetSimpleTypeQualifiedName(type);
if (typeName == null)
{
typeName = GetSimpleTypeQualifiedName(typeof(string));
}
System.Diagnostics.Debug.Assert(typeName != null);
attribute.SchemaTypeName = typeName;
}
private static void FillBoolAttribute(XmlSchemaAttribute attribute)
{
XmlSchemaSimpleType simpleType = new XmlSchemaSimpleType();
XmlSchemaSimpleTypeRestriction restriction = new XmlSchemaSimpleTypeRestriction();
restriction.BaseTypeName = GetSimpleTypeQualifiedName(typeof(string));
XmlSchemaEnumerationFacet enumFacet = new XmlSchemaEnumerationFacet();
enumFacet.Value = bool.TrueString;
restriction.Facets.Add(enumFacet);
enumFacet = new XmlSchemaEnumerationFacet();
enumFacet.Value = bool.FalseString;
restriction.Facets.Add(enumFacet);
simpleType.Content = restriction;
attribute.SchemaType = simpleType;
}
private static void FillEnumAttribute(XmlSchemaAttribute attribute, Type type)
{
XmlSchemaSimpleType simpleType = new XmlSchemaSimpleType();
XmlSchemaSimpleTypeRestriction restriction = new XmlSchemaSimpleTypeRestriction();
restriction.BaseTypeName = GetSimpleTypeQualifiedName(typeof(string));
foreach (string enumValue in Enum.GetNames(type))
{
XmlSchemaEnumerationFacet enumFacet = new XmlSchemaEnumerationFacet();
enumFacet.Value = enumValue;
restriction.Facets.Add(enumFacet);
}
simpleType.Content = restriction;
attribute.SchemaType = simpleType;
}
private static XmlQualifiedName GetSimpleTypeQualifiedName(Type type)
{
if (type == typeof(string))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.String).QualifiedName;
}
else if (type == typeof(byte))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.UnsignedByte).QualifiedName;
}
else if (type == typeof(sbyte))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.Byte).QualifiedName;
}
else if (type == typeof(short))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.Short).QualifiedName;
}
else if (type == typeof(ushort))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.UnsignedShort).QualifiedName;
}
else if (type == typeof(int))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.Int).QualifiedName;
}
else if (type == typeof(uint))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.UnsignedInt).QualifiedName;
}
else if (type == typeof(long))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.Long).QualifiedName;
}
else if (type == typeof(ulong))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.UnsignedLong).QualifiedName;
}
else if (type == typeof(float))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.Float).QualifiedName;
}
else if (type == typeof(double))
{
return XmlSchemaSimpleType.GetBuiltInSimpleType(XmlTypeCode.Double).QualifiedName;
}
return null;
}
private XmlSchemaAnnotation CreateAnnotation(string description)
{
XmlSchemaAnnotation annotation = new XmlSchemaAnnotation();
XmlSchemaDocumentation doc = new XmlSchemaDocumentation();
doc.Markup = TextToNodeArray(description);
annotation.Items.Add(doc);
return annotation;
}
private XmlNode[] TextToNodeArray(string text)
{
return new XmlNode[1] { m_factoryDocument.CreateTextNode(text) };
}
#endregion
#endregion
}
}