//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace Microsoft.Build.Tasks.Xaml { using System.Collections.Generic; using System.Xaml; using System.Collections.ObjectModel; using System.Runtime; using System; using System.ComponentModel; using System.Reflection; using System.Globalization; using XamlBuildTask; public sealed class AttributeData { static CultureInfo invariantEnglishUS = CultureInfo.ReadOnly(new CultureInfo("en-us", false)); List parameters; Dictionary properties; public XamlType Type { get; set; } public IList Parameters { get { if (this.parameters == null) { this.parameters = new List(); } return parameters; } } public IDictionary Properties { get { if (properties == null) { properties = new Dictionary(); } return properties; } } // We get here when we are inside x:ClassAttributes or x:Property.Attributes. We expect the first element to be the Attribute SO. internal static AttributeData LoadAttributeData(XamlReader reader, NamespaceTable namespaceTable, string rootNamespace) { AttributeData attributeData = null; reader.Read(); if (reader.NodeType == XamlNodeType.StartObject) { attributeData = new AttributeData { Type = reader.Type }; bool readNext = false; while (readNext || reader.Read()) { namespaceTable.ManageNamespace(reader); readNext = false; if (reader.NodeType == XamlNodeType.StartMember) { if (reader.Member == XamlLanguage.Arguments) { foreach (AttributeParameterData parameterData in ReadParameters(reader.ReadSubtree(), namespaceTable, rootNamespace)) { attributeData.Parameters.Add(parameterData); } readNext = true; } else if (!reader.Member.IsDirective) { KeyValuePair propertyInfo = ReadAttributeProperty(reader.ReadSubtree(), namespaceTable, rootNamespace); attributeData.Properties.Add(propertyInfo.Key, propertyInfo.Value); readNext = true; } } } } return attributeData; } // Read the Property on the attribute. private static KeyValuePair ReadAttributeProperty(XamlReader reader, NamespaceTable namespaceTable, string rootNamespace) { reader.Read(); Fx.Assert(reader.Member != null, "Member element should not be null"); XamlMember member = reader.Member; AttributeParameterData propertyInfo = new AttributeParameterData(); if (member.Type != null && !member.Type.IsUnknown) { propertyInfo.Type = member.Type; } ReadParamInfo(reader, member.Type, namespaceTable, rootNamespace, propertyInfo); return new KeyValuePair(member.Name, propertyInfo); } // Read the parameters on the Attribute. We expect the parameters to be in the order in which they are supposed to appear in the output code. // Here we are inside x:Arguments and we expect a list of parameters. private static IList ReadParameters(XamlReader reader, NamespaceTable namespaceTable, string rootNamespace) { IList parameters = new List(); bool readNext = false; while (readNext || reader.Read()) { readNext = false; if (reader.NodeType == XamlNodeType.StartObject) { AttributeParameterData paramInfo = new AttributeParameterData(); ReadParamInfo(reader.ReadSubtree(), null, namespaceTable, rootNamespace, paramInfo); parameters.Add(paramInfo); readNext = true; } } return parameters; } // Read the actual parameter info, i.e. the type of the paramter and its value. // The first element could be a V or an SO. private static void ReadParamInfo(XamlReader reader, XamlType type, NamespaceTable namespaceTable, string rootNamespace, AttributeParameterData paramInfo) { reader.Read(); bool readNext = false; do { readNext = false; if (reader.NodeType == XamlNodeType.StartObject && reader.Type == XamlLanguage.Array) { paramInfo.IsArray = true; XamlReader xamlArrayReader = reader.ReadSubtree(); xamlArrayReader.Read(); while (readNext || xamlArrayReader.Read()) { readNext = false; if (xamlArrayReader.NodeType == XamlNodeType.StartMember && xamlArrayReader.Member.Name == "Type") { xamlArrayReader.Read(); if (xamlArrayReader.NodeType == XamlNodeType.Value) { XamlType arrayType = XamlBuildTaskServices.GetXamlTypeFromString(xamlArrayReader.Value as string, namespaceTable, xamlArrayReader.SchemaContext); if (arrayType.UnderlyingType != null) { paramInfo.Type = xamlArrayReader.SchemaContext.GetXamlType(arrayType.UnderlyingType.MakeArrayType()); } else { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.AttributeParameterTypeUnknown(arrayType))); } } } else if (xamlArrayReader.NodeType == XamlNodeType.StartObject) { AttributeParameterData arrayEntry = new AttributeParameterData(); ReadParamInfo(xamlArrayReader.ReadSubtree(), null, namespaceTable, rootNamespace, arrayEntry); paramInfo.AddArrayContentsEntry(arrayEntry); readNext = true; } } } else if (reader.NodeType == XamlNodeType.StartObject || reader.NodeType == XamlNodeType.Value) { paramInfo.IsArray = false; string paramVal; object paramObj = null; XamlType paramType; GetParamValueType(reader.ReadSubtree(), type, namespaceTable, rootNamespace, out paramVal, out paramType, out paramObj); paramInfo.TextValue = paramVal; paramInfo.Type = paramType; paramInfo.Value = paramObj; } } while (readNext || reader.Read()); } // Get the paramter value. If the value is enclosed inside nodes of the type, then get the parameter type as well. // Else infer the type from the type of the property. private static void GetParamValueType(XamlReader reader, XamlType type, NamespaceTable namespaceTable, string rootNamespace, out string paramValue, out XamlType paramType, out Object paramObj) { paramValue = String.Empty; paramType = type; paramObj = null; while (reader.Read()) { if (reader.NodeType == XamlNodeType.Value) { if (paramType != null && paramType.UnderlyingType != null) { if (!IsSupportedParameterType(paramType.UnderlyingType) || paramType.UnderlyingType.IsArray) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.AttributeParamTypeNotSupported(paramType.UnderlyingType.FullName))); } paramValue = reader.Value as string; if (typeof(Type).IsAssignableFrom(paramType.UnderlyingType)) { Tuple result = ParseParameterValueTypeName(paramValue, rootNamespace, reader.SchemaContext, namespaceTable); paramValue = result.Item1; paramObj = result.Item2; } else { paramObj = ParseParameterValue(ref paramValue, paramType); } } else { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.AttributeParameterTypeUnknown(reader.Value as string))); } } else if (reader.NodeType == XamlNodeType.StartObject) { if (reader.Type == XamlLanguage.Null) { paramValue = null; paramType = null; } else if (reader.Type == XamlLanguage.Type) { paramType = reader.SchemaContext.GetXamlType(typeof(Type)); } else { paramType = reader.Type; } } } } internal static bool IsSupportedParameterType(Type type) { if (type.IsArray) { return IsSupportedParameterType(type.GetElementType()); } return type.IsEnum || type.IsPrimitive || typeof(string) == type || typeof(Type).IsAssignableFrom(type); } // Given a live value for an attribute parameter, returns the text that needs to be // code-gened for the value. internal static string GetParameterText(object value, XamlType paramType) { Type type = paramType.UnderlyingType; if (type.IsEnum) { // Note: this doesn't support flags enums with multiple flags set, but neither // does the existing Dev10 code return type.FullName + "." + value.ToString(); } else if (typeof(Type).IsAssignableFrom(type)) { return ((Type)value).FullName; } else if (type == typeof(String)) { return (string)value; } else if (type.IsPrimitive) { TypeConverter typeConverter = paramType.TypeConverter.ConverterInstance; Fx.Assert(typeConverter != null, "All primitives have TypeConverters"); return (string)typeConverter.ConvertTo(null, invariantEnglishUS, value, typeof(string)); } else { throw Fx.AssertAndThrow("Unexpected attribute parameter type"); } } // Given the text for an attribute parameter, parses it to a live value (if possible). internal static object GetParameterValue(ref string paramValue, XamlType paramType) { if (typeof(Type).IsAssignableFrom(paramType.UnderlyingType)) { // We can't convert a CLR type name to a Type because we don't know what assembly it's from return null; } return ParseParameterValue(ref paramValue, paramType); } // Parses a XAML QName to a CLR Type Name (and the corresponding ROL type, if available) private static Tuple ParseParameterValueTypeName(string paramValue, string rootNamespace, XamlSchemaContext schemaContext, NamespaceTable namespaceTable) { XamlType xamlType = XamlBuildTaskServices.GetXamlTypeFromString(paramValue, namespaceTable, schemaContext); string clrTypeName; if (!XamlBuildTaskServices.TryGetClrTypeName(xamlType, rootNamespace, out clrTypeName)) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.TypeNameUnknown(XamlBuildTaskServices.GetFullTypeName(xamlType)))); } return Tuple.Create(clrTypeName, xamlType.UnderlyingType); } // Given a text value for an attribute parameter, attempts to convert it to a live value. // Inverse of GetParameterText. private static object ParseParameterValue(ref string paramValue, XamlType paramType) { object valueObj = null; paramValue = NormalizeParameterText(paramValue, paramType); if (!paramType.UnderlyingType.Assembly.ReflectionOnly) { TypeConverter typeConverter = paramType.TypeConverter.ConverterInstance; if (typeConverter != null && typeConverter.CanConvertFrom(paramValue.GetType())) { try { valueObj = typeConverter.ConvertFrom(null, invariantEnglishUS, paramValue); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } // ----ing exceptions here to avoid throwing on // a format that we don't recognize, but the compiler // might be able to interpret. } } } return valueObj; } // Get the parameter value that is to be put in the generated code as is. private static string NormalizeParameterText(string value, XamlType xamlType) { string paramValue; Type type = xamlType.UnderlyingType; Fx.Assert(!typeof(Type).IsAssignableFrom(type), "This method should not be called for Types"); if (type.IsEnum) { paramValue = type.FullName + "." + value; } else if (type == typeof(String)) { paramValue = value; } else if (type.IsPrimitive) { value = value.TrimStart('"'); value = value.TrimEnd('"'); if (type == typeof(bool)) { if (string.Compare(value, "true", StringComparison.OrdinalIgnoreCase) == 0) { paramValue = "true"; } else if (string.Compare(value, "false", StringComparison.OrdinalIgnoreCase) == 0) { paramValue = "false"; } else { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.UnknownBooleanValue(value))); } } else { paramValue = value; } } else { throw Fx.AssertAndThrow("Unexpected attribute parameter type"); } return paramValue; } } }