void UpdatePageDefinitionsLocalPropertySettings(PageTypePropertyDefinition propertyDefinition, PageTypeDefinition pageTypeDefinition, PropertySettingsContainer container) { List<PropertySettingsUpdater> settingsUpdaters = GetPropertySettingsUpdaters(pageTypeDefinition, propertyDefinition); settingsUpdaters.ForEach(updater => { var wrapper = container.GetSetting(updater.SettingsType); if (wrapper == null) { wrapper = new PropertySettingsWrapper(); container.Settings[updater.SettingsType.FullName] = wrapper; //TODO: Add spec validating that exception is thrown with the below uncommented (An item with the same key has already been added.) //container.Settings.Add(updater.SettingsType.FullName, wrapper); } bool settingsAlreadyExists = true; if (wrapper.PropertySettings == null) { wrapper.PropertySettings = ((IPropertySettings)Activator.CreateInstance(updater.SettingsType)).GetDefaultValues(); settingsAlreadyExists = false; } if (settingsAlreadyExists && !updater.OverWriteExisting) return; int hashBeforeUpdate = updater.GetSettingsHashCode(wrapper.PropertySettings); updater.UpdateSettings(wrapper.PropertySettings); int hashAfterUpdate = updater.GetSettingsHashCode(wrapper.PropertySettings); if (hashBeforeUpdate != hashAfterUpdate || !settingsAlreadyExists) { propertySettingsRepository.Save(container); } }); }
protected internal virtual void UpdatePropertySettings(PageTypeDefinition pageTypeDefinition, PageTypePropertyDefinition propertyDefinition, PageDefinition pageDefinition) { PropertySettingsContainer container = GetPropertySettingsContainer(pageDefinition); UpdatePageDefinitionsGlobalPropertySettings(propertyDefinition, pageTypeDefinition, container); UpdatePageDefinitionsLocalPropertySettings(propertyDefinition, pageTypeDefinition, container); }
protected internal virtual void UpdatePageDefinition(PageDefinition pageDefinition, PageTypePropertyDefinition pageTypePropertyDefinition) { string oldValues = SerializeValues(pageDefinition); UpdatePageDefinitionValues(pageDefinition, pageTypePropertyDefinition); if(SerializeValues(pageDefinition) != oldValues) PageDefinitionFactory.Save(pageDefinition); }
public void GivenPageTypePropertyDefinitionWithNoEditCaption_GetEditCaptionOrName_ReturnsName() { string propertyName = TestValueUtility.CreateRandomString(); PageTypePropertyDefinition definition = new PageTypePropertyDefinition(propertyName, typeof(string), new NativePageType(), new PageTypePropertyAttribute()); string returnedEditCaption = definition.GetEditCaptionOrName(); Assert.Equal<string>(propertyName, returnedEditCaption); }
public void GivenPageTypePropertyDefinitionWithEditCaption_GetEditCaptionOrName_ReturnsEditCaptionFromAttribute() { PageTypePropertyDefinition definition = new PageTypePropertyDefinition(TestValueUtility.CreateRandomString(), typeof(string), new NativePageType(), new PageTypePropertyAttribute(), null); string editCaption = TestValueUtility.CreateRandomString(); definition.PageTypePropertyAttribute.EditCaption = editCaption; string returnedEditCaption = definition.GetEditCaptionOrName(false); Assert.Equal<string>(editCaption, returnedEditCaption); }
protected internal virtual PageDefinition CreateNewPageDefinition(PageTypePropertyDefinition propertyDefinition) { PageDefinition pageDefinition = new PageDefinition(); pageDefinition.PageTypeID = propertyDefinition.PageType.ID; pageDefinition.Name = propertyDefinition.Name; pageDefinition.EditCaption = propertyDefinition.GetEditCaptionOrName(); SetPageDefinitionType(pageDefinition, propertyDefinition); PageDefinitionFactory.Save(pageDefinition); return pageDefinition; }
public virtual void CreateNewPageDefinition(PageTypePropertyDefinition propertyDefinition) { PageDefinition pageDefinition = new PageDefinition(); pageDefinition.PageTypeID = propertyDefinition.PageType.ID; pageDefinition.Name = propertyDefinition.Name; pageDefinition.EditCaption = propertyDefinition.GetEditCaptionOrName(); SetPageDefinitionType(pageDefinition, propertyDefinition); UpdatePageDefinitionValues(pageDefinition, propertyDefinition); pageDefinitionRepository.Save(pageDefinition); }
public virtual void UpdateExistingPageDefinition(PageDefinition pageDefinition, PageTypePropertyDefinition pageTypePropertyDefinition) { string oldValues = SerializeValues(pageDefinition); UpdatePageDefinitionValues(pageDefinition, pageTypePropertyDefinition); string updatedValues = SerializeValues(pageDefinition); if (updatedValues != oldValues) { log.Debug(string.Format("Updating PageDefintion, old values: {0}, new values: {1}.", oldValues, updatedValues)); pageDefinitionRepository.Save(pageDefinition); } }
protected internal virtual void UpdatePropertySettings(PageTypeDefinition pageTypeDefinition, PageTypePropertyDefinition propertyDefinition, PageDefinition pageDefinition) { PropertySettingsContainer container = GetPropertySettingsContainer(pageDefinition); object[] attributes = GetPropertyAttributes(propertyDefinition, pageTypeDefinition); var useGlobalSettingsAttribute = attributes.OfType<UseGlobalSettingsAttribute>().FirstOrDefault(); if(useGlobalSettingsAttribute != null) { //TODO: Should validate not null and valid type at startup var globalSettingsUpdater = globalPropertySettingsLocator.GetGlobalPropertySettingsUpdaters().Where(u => u.WrappedInstanceType == useGlobalSettingsAttribute.Type).First(); var wrapper =_propertySettingsRepository.GetGlobals(globalSettingsUpdater.SettingsType) .Where(w => globalSettingsUpdater.Match(w)) .First(); container.Settings[globalSettingsUpdater.SettingsType.FullName] = wrapper; //TODO: Add spec validating that exception is thrown with the below uncommented (An item with the same key has already been added.) //container.Settings.Add(globalSettingsUpdater.SettingsType.FullName, wrapper); _propertySettingsRepository.Save(container); } List<PropertySettingsUpdater> settingsUpdaters = GetPropertySettingsUpdaters(pageTypeDefinition, propertyDefinition, pageDefinition); settingsUpdaters.ForEach(updater => { var wrapper = container.GetSetting(updater.SettingsType); if (wrapper == null) { wrapper = new PropertySettingsWrapper(); container.Settings[updater.SettingsType.FullName] = wrapper; //TODO: Add spec validating that exception is thrown with the below uncommented (An item with the same key has already been added.) //container.Settings.Add(updater.SettingsType.FullName, wrapper); } bool settingsAlreadyExists = true; if (wrapper.PropertySettings == null) { wrapper.PropertySettings = ((IPropertySettings)Activator.CreateInstance(updater.SettingsType)).GetDefaultValues(); settingsAlreadyExists = false; } if (settingsAlreadyExists && !updater.OverWriteExisting) return; int hashBeforeUpdate = updater.GetSettingsHashCode(wrapper.PropertySettings); updater.UpdateSettings(wrapper.PropertySettings); int hashAfterUpdate = updater.GetSettingsHashCode(wrapper.PropertySettings); if (hashBeforeUpdate != hashAfterUpdate || !settingsAlreadyExists) { _propertySettingsRepository.Save(container); } }); }
public void GivenPageTypeWithPropertyAndCorrespondingPropertyDefition_GetExistingPropertyDefinition_ReturnsPageDefinition() { PageTypePropertyUpdater utility = PageTypePropertyUpdaterFactory.Create(); IPageType pageType = new NativePageType(); string name = TestValueUtility.CreateRandomString(); Type type = typeof(string); PageTypePropertyAttribute attribute = new PageTypePropertyAttribute(); PageTypePropertyDefinition pageTypePropertyDefinition = new PageTypePropertyDefinition(name, type, pageType, attribute); PageDefinition pageDefinition = new PageDefinition(); pageDefinition.Name = name; pageType.Definitions.Add(pageDefinition); PageDefinition returnedPageDefinition = utility.GetExistingPageDefinition(pageType, pageTypePropertyDefinition); Assert.Equal<PageDefinition>(pageDefinition, returnedPageDefinition); }
protected internal virtual void UpdatePropertySettings(PageTypeDefinition pageTypeDefinition, PageTypePropertyDefinition propertyDefinition, PageDefinition pageDefinition) { var prop = pageTypeDefinition.Type.GetProperties().Where(p => p.Name == propertyDefinition.Name).FirstOrDefault (); var attributes = prop.GetCustomAttributes(typeof(PropertySettingsAttribute), true); foreach (var attribute in attributes) { PropertySettingsContainer container; if (pageDefinition.SettingsID == Guid.Empty) { pageDefinition.SettingsID = Guid.NewGuid(); PageDefinitionFactory.Save(pageDefinition); container = new PropertySettingsContainer(pageDefinition.SettingsID); } else { if (!_propertySettingsRepository.TryGetContainer(pageDefinition.SettingsID, out container)) { container = new PropertySettingsContainer(pageDefinition.SettingsID); } } var settingsAttribute = (PropertySettingsAttribute) attribute; var wrapper = container.GetSetting(settingsAttribute.SettingType); if (wrapper == null) { wrapper = new PropertySettingsWrapper(); container.Settings.Add(settingsAttribute.SettingType.FullName, wrapper); } bool settingsAlreadyExists = true; if (wrapper.PropertySettings == null) { wrapper.PropertySettings = (IPropertySettings) Activator.CreateInstance(settingsAttribute.SettingType); settingsAlreadyExists = false; } if(settingsAlreadyExists && !settingsAttribute.OverWriteExistingSettings) return; if(settingsAttribute.UpdateSettings(wrapper.PropertySettings) || !settingsAlreadyExists) _propertySettingsRepository.Save(container); } }
void UpdatePageDefinitionsGlobalPropertySettings(PageTypePropertyDefinition propertyDefinition, PageTypeDefinition pageTypeDefinition, PropertySettingsContainer container) { object[] attributes = GetPropertyAttributes(propertyDefinition, pageTypeDefinition); var useGlobalSettingsAttribute = attributes.OfType<UseGlobalSettingsAttribute>().FirstOrDefault(); if (useGlobalSettingsAttribute != null) { //TODO: Should validate not null and valid type at startup var globalSettingsUpdater = globalPropertySettingsLocator.GetGlobalPropertySettingsUpdaters().Where(u => u.WrappedInstanceType == useGlobalSettingsAttribute.Type).First(); var wrapper = propertySettingsRepository.GetGlobals(globalSettingsUpdater.SettingsType) .Where(w => globalSettingsUpdater.Match(w)) .First(); container.Settings[globalSettingsUpdater.SettingsType.FullName] = wrapper; //TODO: Add spec validating that exception is thrown with the below uncommented (An item with the same key has already been added.) //container.Settings.Add(globalSettingsUpdater.SettingsType.FullName, wrapper); propertySettingsRepository.Save(container); } }
public virtual void UpdateExistingPageDefinition(PageDefinition pageDefinition, PageTypePropertyDefinition pageTypePropertyDefinition) { string oldValues = SerializeValues(pageDefinition); UpdatePageDefinitionValues(pageDefinition, pageTypePropertyDefinition); string updatedValues = SerializeValues(pageDefinition); if (updatedValues != oldValues) { string diff = ValueDiff(oldValues, updatedValues); log.DebugFormat( "Updating PageDefintion for page {0}({1}), diff: {2}, old values: {3}, new values: {4}.", pageTypePropertyDefinition.PageType.ID, pageTypePropertyDefinition.PageType.Name, diff, oldValues, updatedValues); pageDefinitionRepository.Save(pageDefinition); } }
void UpdatePageDefinitionsGlobalPropertySettings(PageTypePropertyDefinition propertyDefinition, PageTypeDefinition pageTypeDefinition, PageDefinition pageDefinition) { IEnumerable<object> attributes = GetPropertyAttributes(propertyDefinition, pageTypeDefinition); var useGlobalSettingsAttribute = attributes.OfType<UseGlobalSettingsAttribute>().FirstOrDefault(); if (useGlobalSettingsAttribute != null) { var container = GetPropertySettingsContainer(pageDefinition); //TODO: Should validate not null and valid type at startup var globalSettingsUpdater = globalPropertySettingsLocator.GetGlobalPropertySettingsUpdaters().First(u => u.WrappedInstanceType == useGlobalSettingsAttribute.Type); var wrapper = _propertySettingsRepositoryMethod().GetGlobals(globalSettingsUpdater.SettingsType) .First(w => globalSettingsUpdater.Match(w)); PropertySettingsWrapper existingWrapper = container.Settings.ContainsKey(globalSettingsUpdater.SettingsType.FullName) ? container.Settings[globalSettingsUpdater.SettingsType.FullName] : null; if (existingWrapper == null || existingWrapper.Id != wrapper.Id) { container.Settings[globalSettingsUpdater.SettingsType.FullName] = wrapper; //TODO: Add spec validating that exception is thrown with the below uncommented (An item with the same key has already been added.) //container.Settings.Add(globalSettingsUpdater.SettingsType.FullName, wrapper); _propertySettingsRepositoryMethod().Save(container); AddToUpdatedCacheKeys(container); } } }
protected internal virtual void UpdatePageDefinitionValues(PageDefinition pageDefinition, PageTypePropertyDefinition pageTypePropertyDefinition) { PageTypePropertyAttribute propertyAttribute = pageTypePropertyDefinition.PageTypePropertyAttribute; pageDefinition.EditCaption = pageTypePropertyDefinition.GetEditCaptionOrName(); pageDefinition.HelpText = propertyAttribute.HelpText ?? string.Empty; pageDefinition.Required = propertyAttribute.Required; pageDefinition.Searchable = propertyAttribute.Searchable; pageDefinition.DefaultValue = propertyAttribute.DefaultValue != null ? propertyAttribute.DefaultValue.ToString() : string.Empty; pageDefinition.DefaultValueType = propertyAttribute.DefaultValueType; pageDefinition.LanguageSpecific = propertyAttribute.UniqueValuePerLanguage; pageDefinition.DisplayEditUI = propertyAttribute.DisplayInEditMode; pageDefinition.FieldOrder = propertyAttribute.SortOrder; UpdateLongStringSettings(pageDefinition, propertyAttribute); UpdatePageDefinitionTab(pageDefinition, propertyAttribute); }
private static IEnumerable<object> GetPropertyAttributes(PageTypePropertyDefinition propertyDefinition, PageTypeDefinition pageTypeDefinition) { // Binding flags supporting both public and non-public instance properties const BindingFlags propertyBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; PropertyInfo prop = null; int propertyGroupDashIndex = propertyDefinition.Name.IndexOf("-", StringComparison.Ordinal); if (propertyGroupDashIndex >= 0) { // the property definition is a property belonging to a property group string propertyGroupPropertyName = propertyDefinition.Name.Substring(0, propertyGroupDashIndex); string propertyName = propertyDefinition.Name.Substring(propertyGroupDashIndex + 1); PropertyInfo propertyGroupProperty = pageTypeDefinition.Type.GetProperties(propertyBindingFlags).FirstOrDefault(p => String.Equals(p.Name, propertyGroupPropertyName)); //if (propertyGroupProperty == null) //{ // // TODO: Enable exceptions for a development fail-fast mode? // var message = String.Format("Unable to locate the property group-property \"{0}\" in PageType \"{1}\".", // propertyGroupPropertyName, pageTypeDefinition.GetPageTypeName()); // throw new PageTypeBuilderException(message); //} if (propertyGroupProperty != null) { prop = propertyGroupProperty.PropertyType.GetProperties().FirstOrDefault(p => String.Equals(p.Name, propertyName)); } } else { prop = pageTypeDefinition.Type.GetProperties(propertyBindingFlags).FirstOrDefault(p => String.Equals(p.Name, propertyDefinition.Name)); } if (prop == null) { // TODO: Enable exceptions for a development fail-fast mode? This is a serious error that could else be harder to find. //var message = String.Format("Unable to locate the property \"{0}\" in PageType \"{1}\".", // propertyDefinition.Name, pageTypeDefinition.GetPageTypeName()); //throw new PageTypeBuilderException(message); return Enumerable.Empty<object>(); } return prop.GetCustomAttributes(true); }
protected internal virtual void UpdatePropertySettings(PageTypeDefinition pageTypeDefinition, PageTypePropertyDefinition propertyDefinition, PageDefinition pageDefinition) { UpdatePageDefinitionsGlobalPropertySettings(propertyDefinition, pageTypeDefinition, pageDefinition); UpdatePageDefinitionsLocalPropertySettings(propertyDefinition, pageTypeDefinition, pageDefinition); }
private object[] GetPropertyAttributes(PageTypePropertyDefinition propertyDefinition, PageTypeDefinition pageTypeDefinition) { PropertyInfo prop; if (propertyDefinition.Name.Contains("-")) { // the property definition is a property belonging to a property group int index = propertyDefinition.Name.IndexOf("-"); string propertyGroupPropertyName = propertyDefinition.Name.Substring(0, index); string propertyName = propertyDefinition.Name.Substring(index + 1); PropertyInfo propertyGroupProperty = pageTypeDefinition.Type.GetProperties().Where(p => string.Equals(p.Name, propertyGroupPropertyName)).FirstOrDefault(); prop = propertyGroupProperty.PropertyType.GetProperties().Where(p => string.Equals(p.Name, propertyName)).FirstOrDefault(); } else prop = pageTypeDefinition.Type.GetProperties().Where(p => string.Equals(p.Name, propertyDefinition.Name)).FirstOrDefault(); return prop.GetCustomAttributes(true); }
private List<PropertySettingsUpdater> GetPropertySettingsUpdaters(PageTypeDefinition pageTypeDefinition, PageTypePropertyDefinition propertyDefinition, PageDefinition pageDefinition) { object[] attributes = GetPropertyAttributes(propertyDefinition, pageTypeDefinition); var settingsUpdaters = new List<PropertySettingsUpdater>(); foreach (var attribute in attributes) { foreach (var interfaceType in attribute.GetType().GetInterfaces()) { if (!interfaceType.IsGenericType) continue; if(!typeof (IUpdatePropertySettings<>).IsAssignableFrom(interfaceType.GetGenericTypeDefinition())) continue; var settingsType = interfaceType.GetGenericArguments().First(); var updater = new PropertySettingsUpdater(settingsType, attribute); settingsUpdaters.Add(updater); } } return settingsUpdaters; }
protected internal virtual PageDefinition GetExistingPageDefinition(IPageType pageType, PageTypePropertyDefinition propertyDefinition) { return pageType.Definitions.FirstOrDefault(definition => definition.Name == propertyDefinition.Name); }
protected virtual PageDefinition GetExistingPageDefinition(IPageType pageType, PageTypePropertyDefinition propertyDefinition) { var definitions = pageTypeRepository.Load(pageType.ID).Definitions; return definitions.FirstOrDefault(definition => string.Equals(definition.Name, propertyDefinition.Name, StringComparison.OrdinalIgnoreCase)); }
private static List<PropertySettingsUpdater> GetPropertySettingsUpdaters(PageTypeDefinition pageTypeDefinition, PageTypePropertyDefinition propertyDefinition) { var settingsUpdaters = from attribute in GetPropertyAttributes(propertyDefinition, pageTypeDefinition) from interfaceType in attribute.GetType().GetInterfaces() where interfaceType.IsGenericType && typeof(IUpdatePropertySettings<>).IsAssignableFrom(interfaceType.GetGenericTypeDefinition()) let settingsType = interfaceType.GetGenericArguments().First() select new PropertySettingsUpdater(settingsType, attribute); return settingsUpdaters.ToList(); }
protected internal virtual void SetPageDefinitionType(PageDefinition pageDefinition, PageTypePropertyDefinition propertyDefinition) { pageDefinition.Type = GetPageDefinitionType(propertyDefinition); }
protected internal virtual PageDefinitionType GetPageDefinitionType(PageTypePropertyDefinition definition) { return GetPageDefinitionType( definition.PageType.Name, definition.Name, definition.PropertyType, definition.PageTypePropertyAttribute); }
public virtual PageDefinitionType GetPageDefinitionType(PageTypePropertyDefinition definition) { return GetPageDefinitionType( definition.PageType.Name, definition.Name, definition.PropertyType, definition.PageTypePropertyAttribute); }
protected internal virtual PageDefinitionType GetPageDefinitionType(PageTypePropertyDefinition definition) { return PageDefinitionTypeMapper.GetPageDefinitionType(definition); }
protected virtual void UpdatePageDefinitionValues(PageDefinition pageDefinition, PageTypePropertyDefinition pageTypePropertyDefinition) { pageDefinition.Name = pageTypePropertyDefinition.Name; PageTypePropertyAttribute propertyAttribute = pageTypePropertyDefinition.PageTypePropertyAttribute; var specifiedType = GetPageDefinitionType(pageTypePropertyDefinition); var currentType = pageDefinition.Type; if(specifiedType.DataType == currentType.DataType) pageDefinition.Type = specifiedType; if (CanModifyProperty(pageDefinition, propertyAttribute.EditCaptionSet)) pageDefinition.EditCaption = pageTypePropertyDefinition.GetEditCaptionOrName(); else if (!propertyAttribute.EditCaptionSet && string.IsNullOrEmpty(pageDefinition.EditCaption)) pageDefinition.EditCaption = pageTypePropertyDefinition.GetEditCaptionOrName(); if (CanModifyProperty(pageDefinition, propertyAttribute.HelpTextSet)) pageDefinition.HelpText = propertyAttribute.HelpText ?? string.Empty; if (CanModifyProperty(pageDefinition, propertyAttribute.RequiredSet)) pageDefinition.Required = propertyAttribute.Required; if (CanModifyProperty(pageDefinition, propertyAttribute.SearchableSet)) pageDefinition.Searchable = propertyAttribute.Searchable; if (CanModifyProperty(pageDefinition, propertyAttribute.DefaultValueSet)) pageDefinition.DefaultValue = propertyAttribute.DefaultValue != null ? propertyAttribute.DefaultValue.ToString() : string.Empty; if (CanModifyProperty(pageDefinition, propertyAttribute.DefaultValueTypeSet)) pageDefinition.DefaultValueType = propertyAttribute.DefaultValueType; if (CanModifyProperty(pageDefinition, propertyAttribute.UniqueValuePerLanguageSet)) pageDefinition.LanguageSpecific = propertyAttribute.UniqueValuePerLanguage; if (CanModifyProperty(pageDefinition, propertyAttribute.DisplayInEditModeSet)) pageDefinition.DisplayEditUI = propertyAttribute.DisplayInEditMode; if (CanModifyProperty(pageDefinition, propertyAttribute.SortOrderSet)) pageDefinition.FieldOrder = GetFieldOrder(pageDefinition, propertyAttribute); if (CanModifyProperty(pageDefinition, propertyAttribute.TabSet)) UpdatePageDefinitionTab(pageDefinition, propertyAttribute); }
public void GivenPageTypePropertyDefinitionWithNoTypeAndNonMappedPropertyType_GetPageDefinitionType_ThrowsException() { PageDefinitionTypeMapper mapper = new PageDefinitionTypeMapper(null); Type unmappedType = typeof(StringBuilder); PageTypePropertyDefinition definition = new PageTypePropertyDefinition( TestValueUtility.CreateRandomString(), unmappedType, new NativePageType(), new PageTypePropertyAttribute()); Exception exception = Record.Exception(() => { mapper.GetPageDefinitionType(definition); }); Assert.NotNull(exception); }
protected virtual void UpdatePageDefinitionValues(PageDefinition pageDefinition, PageTypePropertyDefinition pageTypePropertyDefinition) { PageTypePropertyAttribute propertyAttribute = pageTypePropertyDefinition.PageTypePropertyAttribute; var specifiedType = GetPageDefinitionType(pageTypePropertyDefinition); var currentType = pageDefinition.Type; if(specifiedType.DataType == currentType.DataType) { pageDefinition.Type = specifiedType; } pageDefinition.EditCaption = pageTypePropertyDefinition.GetEditCaptionOrName(); pageDefinition.HelpText = propertyAttribute.HelpText ?? string.Empty; pageDefinition.Required = propertyAttribute.Required; pageDefinition.Searchable = propertyAttribute.Searchable; pageDefinition.DefaultValue = propertyAttribute.DefaultValue != null ? propertyAttribute.DefaultValue.ToString() : string.Empty; pageDefinition.DefaultValueType = propertyAttribute.DefaultValueType; pageDefinition.LanguageSpecific = propertyAttribute.UniqueValuePerLanguage; pageDefinition.DisplayEditUI = propertyAttribute.DisplayInEditMode; pageDefinition.FieldOrder = GetFieldOrder(pageDefinition, propertyAttribute); UpdatePageDefinitionTab(pageDefinition, propertyAttribute); }