public PackageVersionVariable(ProjectPropertyElement element, string version, bool isReadOnly)
 {
     _element   = element ?? throw new ArgumentNullException(nameof(element));
     IsReadOnly = isReadOnly;
     Name       = element.Name.ToString();
     Version    = version ?? string.Empty;
 }
Example #2
0
        /// <summary>
        /// Renames a value inside a delimited property.
        /// </summary>
        /// <param name="project">Xml representation of the MsBuild project.</param>
        /// <param name="evaluatedPropertyValue">Original evaluated value of the property.</param>
        /// <param name="propertyName">Property name.</param>
        /// <param name="oldValue">Current property value.</param>
        /// <param name="newValue">New property value.</param>
        /// <param name="delimiter">Character used to delimit the property values.</param>
        /// <remarks>
        /// If the property is not present it will be added. This means that the evaluated property
        /// value came from one of the project imports.
        /// </remarks>
        public static void RenamePropertyValue(ProjectRootElement project, string evaluatedPropertyValue, string propertyName, string oldValue, string newValue, char delimiter = ';')
        {
            Requires.NotNull(project, nameof(project));
            Requires.NotNull(evaluatedPropertyValue, nameof(evaluatedPropertyValue));
            Requires.NotNullOrEmpty(propertyName, nameof(propertyName));

            ProjectPropertyElement property = GetOrAddProperty(project, propertyName);
            var  value      = new StringBuilder();
            bool valueFound = false;

            foreach (string propertyValue in GetPropertyValues(evaluatedPropertyValue, delimiter))
            {
                if (value.Length != 0)
                {
                    value.Append(delimiter);
                }

                if (string.Equals(propertyValue, oldValue, StringComparisons.PropertyValues))
                {
                    value.Append(newValue);
                    valueFound = true;
                }
                else
                {
                    value.Append(propertyValue);
                }
            }

            property.Value = value.ToString();

            if (!valueFound)
            {
                throw new ArgumentException(string.Format(Resources.MsBuildMissingValueToRename, oldValue, propertyName), nameof(oldValue));
            }
        }
Example #3
0
            //=====================================================================

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="owner">The owning dialog</param>
            /// <param name="buildProperty">The build property to edit or null
            /// for a new property</param>
            public PropertyItem(UserDefinedPropertyEditorDlg owner,
                                ProjectPropertyElement buildProperty)
            {
                string newPropName;
                int    idx = 1;

                this.Owner     = owner;
                this.buildProp = buildProperty;

                if (buildProp != null)
                {
                    name      = buildProp.Name;
                    condition = buildProp.Condition;
                    propValue = buildProp.Value;
                }
                else
                {
                    do
                    {
                        newPropName = "NewProperty" + idx.ToString(
                            CultureInfo.InvariantCulture);
                        idx++;
                    } while(!this.Owner.Project.IsValidUserDefinedPropertyName(newPropName) ||
                            this.Owner.UserDefinedProperties.Where(
                                p => p.Name == newPropName).Count() != 0);

                    name      = newPropName;
                    propValue = String.Empty;
                }
            }
Example #4
0
 public XmlProjectProperty(Project project, ProjectPropertyElement xml, PropertyType propertyType, bool isImported)
     : base(project, propertyType, xml.Name)
 {
     this.xml         = xml;
     this.is_imported = isImported;
     UpdateEvaluatedValue();
 }
        /// <summary>
        /// Gets a property from the given project.
        /// </summary>
        /// <param name="project">The project from which the property will be retrieved.</param>
        /// <param name="name">The name of the property.</param>
        /// <returns>The value of the specified property, or null if it does not exist.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown if <c>project</c> or <c>name</c> is null.</exception>
        /// <exception cref="System.ArgumentException">Thrown if <c>name</c> is empty.</exception>
        public string GetProperty(IVsProject project, string name)
        {
            if (project == null)
            {
                throw new ArgumentNullException("project");
            }
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }
            if (name.Length == 0)
            {
                throw new ArgumentException(Resources.EmptyProperty, "name");
            }

            ProjectRootElement     msbuildProject = MSBuildProjectFromIVsProject(project).Xml;
            ProjectPropertyElement property       = FindProperty(msbuildProject, name);

            if (property == null)
            {
                return(null);
            }
            else
            {
                return(property.Value);
            }
        }
Example #6
0
        public void WriteProjectInfo_AnalysisFileList_FilesTypes_SpecifiedPlusDefaults()
        {
            // Arrange
            string rootInputFolder  = TestUtils.CreateTestSpecificFolder(this.TestContext, "Inputs");
            string rootOutputFolder = TestUtils.CreateTestSpecificFolder(this.TestContext, "Outputs");

            ProjectDescriptor  descriptor  = BuildUtilities.CreateValidProjectDescriptor(rootInputFolder, "fileTypes.proj.txt");
            ProjectRootElement projectRoot = CreateInitializedProject(descriptor, new WellKnownProjectProperties(), rootOutputFolder);

            // Files we don't expect to be included by default
            string fooType1 = AddFileToProject(projectRoot, "fooType", sonarQubeExclude: null);
            string xxxType1 = AddFileToProject(projectRoot, "xxxType", sonarQubeExclude: null);

            AddFileToProject(projectRoot, "barType", sonarQubeExclude: null);

            // Files we'd normally expect to be included by default
            string managed1 = AddFileToProject(projectRoot, TargetProperties.ItemType_Compile, sonarQubeExclude: null);
            string content1 = AddFileToProject(projectRoot, TargetProperties.ItemType_Content, sonarQubeExclude: null);

            // Update the "item types" property to add some extra item type
            // NB this has to be done *after* the integration targets have been imported
            ProjectPropertyGroupElement group = projectRoot.CreatePropertyGroupElement();

            projectRoot.AppendChild(group);
            ProjectPropertyElement prop = group.AddProperty("SQAnalysisFileItemTypes", "fooType;$(SQAnalysisFileItemTypes);xxxType");

            projectRoot.Save();

            // Act
            ProjectInfo projectInfo = ExecuteWriteProjectInfo(projectRoot, rootOutputFolder);

            // Assert
            AssertResultFileExists(projectInfo, AnalysisType.FilesToAnalyze, fooType1, xxxType1, content1, managed1);
        }
        /// <summary>
        /// Sets a property in the given project, overriding the existing value if it exists.
        /// </summary>
        /// <param name="project">The project in which the property will be set.</param>
        /// <param name="name">The name of the property.</param>
        /// <param name="value">The value of the property.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if <c>project</c>, <c>name</c>, or <c>value</c> is null.</exception>
        /// <exception cref="System.ArgumentException">Thrown if <c>name</c> is empty or contains invalid characters.</exception>
        public void SetProperty(IVsProject project, string name, string value)
        {
            if (project == null)
            {
                throw new ArgumentNullException("project");
            }
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            if (name.Length == 0)
            {
                throw new ArgumentException(Resources.EmptyProperty, "name");
            }

            Project msbuildProject          = MSBuildProjectFromIVsProject(project);
            ProjectPropertyElement property = FindProperty(msbuildProject.Xml, name);

            if (property == null)
            {
                ProjectPropertyGroupElement group = msbuildProject.Xml.AddPropertyGroup();
                group.AddProperty(name, value);
            }
            else
            {
                property.Value = value;
            }
        }
        public static bool AddProjectFlavorIfNotExists(Microsoft.Build.Evaluation.Project project, String flavor)
        {
            ProjectPropertyElement property = project.Xml.Properties.FirstOrDefault(
                p => p.Name.Equals("ProjectTypeGuids"));

            if (property != null)
            {
                if (property.Value.IndexOf(flavor) == -1)
                {
                    if (String.IsNullOrEmpty(property.Value))
                    {
                        property.Value = String.Format("{0};{1}", flavor, CSharpProjectGUI);
                    }
                    else
                    {
                        property.Value = String.Format("{0};{1}", flavor, property.Value);
                    }
                    return(true); //ProjectTypeGuids updated
                }
                else
                {
                    return(false); //ProjectTypeGuids already has this flavor
                }
            }

            // ProjectTypeGuids not present
            project.Xml.AddProperty("ProjectTypeGuids", String.Format("{0};{1}", flavor, CSharpProjectGUI));
            return(true);
        }
        public static void SetProperty(Microsoft.Build.Evaluation.Project project, String label, String name, String value)
        {
            ProjectPropertyGroupElement group = project.Xml.PropertyGroups.FirstOrDefault(g => g.Label.Equals(label));

            if (group == null)
            {
                //
                // Create our property group after the main language targets are imported so we can use the properties
                // defined in this files.
                //
                ProjectImportElement import = project.Xml.Imports.FirstOrDefault(
                    p => (p.Project.IndexOf("Microsoft.Cpp.targets") != -1 || p.Project.IndexOf("IceBuilder.CSharp.props") != -1));
                if (import != null)
                {
                    group = project.Xml.CreatePropertyGroupElement();
                    project.Xml.InsertAfterChild(group, import);
                }
                else
                {
                    group = project.Xml.CreatePropertyGroupElement();
                }
                group.Label = label;
            }

            ProjectPropertyElement property = group.Properties.FirstOrDefault(p => p.Name.Equals(name));

            if (property != null)
            {
                property.Value = value;
            }
            else
            {
                group.AddProperty(name, value);
            }
        }
        public ProjectBuilder AddProperty(params Property[] properties)
        {
            if (properties == null || properties.Length == 0)
            {
                // if call .AddProperty() but not specifying a property just return;
                return(this);
            }

            // If no property group is created then create it automatically.
            if (_lastPropertyGroupElement == null)
            {
                AddPropertyGroup();
            }
            _lastPropertyElements.Clear();
            foreach (var property in properties)
            {
                ProjectPropertyElement projectProperty = ProjectRoot.CreatePropertyElement(property.Name);
                projectProperty.Value     = property.Value;
                projectProperty.Label     = property.Label;
                projectProperty.Condition = property.Condition;

                _lastPropertyGroupElement.AppendChild(projectProperty);
                _lastPropertyElements.Add(projectProperty);
            }
            return(this);
        }
        public static bool HasProjectFlavor(Microsoft.Build.Evaluation.Project project, string flavor)
        {
            ProjectPropertyElement property = project.Xml.Properties.FirstOrDefault(
                p => p.Name.Equals("ProjectTypeGuids", StringComparison.CurrentCultureIgnoreCase));

            return(property != null && property.Value.IndexOf(flavor) != -1);
        }
Example #12
0
        /// <exception cref="ArgumentException">Value cannot be null or whitespace.</exception>
        /// <exception cref="InvalidProjectFileException">If the evaluation fails.</exception>
        public void Fix(
            [NotNull] Project project)
        {
            if (project == null)
            {
                throw new ArgumentNullException(nameof(project));
            }

            var needsNewPropertyGroup = true;

            /* First, remove all properties to replace with a configuration condition */
            foreach (ProjectPropertyGroupElement propertyGroupElement in
                     GetPropertyGroupsWithPropertyToReplace(project))
            {
                if (propertyGroupElement.Condition == string.Empty)
                {
                    /* This property group already defines the property for all configurations. We only need to set it
                     * to the requested value. */
                    needsNewPropertyGroup = false;
                    propertyGroupElement.SetProperty(_propertyName, _propertyValue);
                    continue;
                }

                ProjectPropertyElement property =
                    propertyGroupElement.Properties.Single(p => p.Name == _propertyName);
                propertyGroupElement.RemoveChild(property);
            }

            if (needsNewPropertyGroup)
            {
                /* Add PropertyGroup containing LangVersion for all build configurations */
                ProjectPropertyGroupElement newPropertyGroup = project.Xml.AddPropertyGroup();
                newPropertyGroup.AddProperty(_propertyName, _propertyValue);
            }
        }
        /// <summary>
        /// Renames a value inside a delimited property.
        /// </summary>
        /// <param name="project">Xml representation of the MsBuild project.</param>
        /// <param name="evaluatedPropertyValue">Original evaluated value of the property.</param>
        /// <param name="propertyName">Property name.</param>
        /// <param name="valueToRemove">Value to remove from the property.</param>
        /// <param name="delimiter">Character used to delimit the property values.</param>
        /// <remarks>
        /// If the property is not present it will be added. This means that the evaluated property
        /// value came from one of the project imports.
        /// </remarks>
        public static void RemovePropertyValue(ProjectRootElement project, string evaluatedPropertyValue, string propertyName, string valueToRemove, char delimiter = ';')
        {
            Requires.NotNull(project, nameof(project));
            Requires.NotNull(evaluatedPropertyValue, nameof(evaluatedPropertyValue));
            Requires.NotNullOrEmpty(propertyName, nameof(propertyName));

            ProjectPropertyElement property = GetOrAddProperty(project, propertyName);
            var  newValue   = new StringBuilder();
            bool valueFound = false;

            foreach (string value in GetPropertyValues(evaluatedPropertyValue, delimiter))
            {
                if (string.Compare(value, valueToRemove, StringComparison.Ordinal) != 0)
                {
                    newValue.Append(value);
                    newValue.Append(delimiter);
                }
                else
                {
                    valueFound = true;
                }
            }

            property.Value = newValue.ToString().TrimEnd(delimiter);

            if (!valueFound)
            {
                throw new ArgumentException(string.Format(Resources.MsBuildMissingValueToRemove, valueToRemove, propertyName), nameof(valueToRemove));
            }
        }
        public static bool AddProjectFlavorIfNotExists(Microsoft.Build.Evaluation.Project project, string flavor)
        {
            ProjectPropertyElement property = project.Xml.Properties.FirstOrDefault(
                p => p.Name.Equals("ProjectTypeGuids", StringComparison.CurrentCultureIgnoreCase));

            if (property != null)
            {
                if (property.Value.IndexOf(flavor) == -1)
                {
                    DTEUtil.EnsureFileIsCheckout(project.FullPath);
                    if (string.IsNullOrEmpty(property.Value))
                    {
                        property.Value = string.Format("{0};{1}", flavor, CSharpProjectGUI);
                    }
                    else
                    {
                        property.Value = string.Format("{0};{1}", flavor, property.Value);
                    }
                    return(true); //ProjectTypeGuids updated
                }
                else
                {
                    return(false); //ProjectTypeGuids already has this flavor
                }
            }

            // ProjectTypeGuids not present
            DTEUtil.EnsureFileIsCheckout(project.FullPath);
            project.Xml.AddProperty("ProjectTypeGuids", string.Format("{0};{1}", flavor, CSharpProjectGUI));
            return(true);
        }
Example #15
0
        protected virtual void CopyProperties(IProject sourceProject, IProject targetProject)
        {
            MSBuildBasedProject sp = sourceProject as MSBuildBasedProject;
            MSBuildBasedProject tp = targetProject as MSBuildBasedProject;

            if (sp != null && tp != null)
            {
                lock (sp.SyncRoot) {
                    lock (tp.SyncRoot) {
                        // Remove all PropertyGroups in target project:
                        foreach (ProjectPropertyGroupElement tpg in tp.MSBuildProjectFile.PropertyGroups)
                        {
                            tp.MSBuildProjectFile.RemoveChild(tpg);
                        }
                        // Copy all PropertyGroups from source project to target project:
                        foreach (ProjectPropertyGroupElement spg in sp.MSBuildProjectFile.PropertyGroups)
                        {
                            ProjectPropertyGroupElement tpg = tp.MSBuildProjectFile.AddPropertyGroup();
                            tpg.Condition = spg.Condition;
                            foreach (ProjectPropertyElement sprop in spg.Properties)
                            {
                                ProjectPropertyElement tprop = tpg.AddProperty(sprop.Name, sprop.Value);
                                tprop.Condition = sprop.Condition;
                            }
                        }

                        // use the newly created IdGuid instead of the copied one
                        tp.SetProperty(MSBuildBasedProject.ProjectGuidPropertyName, tp.IdGuid);
                    }
                }
            }
        }
        /// <summary>
        /// Adds a property element to the current &lt;PropertyGroup /&gt;.  A property group is automatically added if necessary.  if <paramref name="unevaluatedValue"/> is <code>null</code>, the property is not added.
        /// </summary>
        /// <param name="propertyGroup">The <see cref="ProjectPropertyGroupElement"/> to add the property to.</param>
        /// <param name="name">The name of the property.</param>
        /// <param name="unevaluatedValue">The unevaluated value of the property.</param>
        /// <param name="condition">An optional condition to add to the property.</param>
        /// <param name="setIfEmpty">An optional value indicating whether or not a condition should be added that checks if the property has already been set.</param>
        /// <param name="label">An option label to add to the property.</param>
        /// <returns>The current <see cref="ProjectCreator"/>.</returns>
        /// <remarks>
        /// The <paramref name="setIfEmpty"/> parameter will add a condition such as " '$(Property)' == '' " which will only set the property if it has not already been set.
        /// </remarks>
        private ProjectCreator Property(ProjectPropertyGroupElement propertyGroup, string name, string unevaluatedValue, string condition = null, bool setIfEmpty = false, string label = null)
        {
            if (unevaluatedValue == null)
            {
                return(this);
            }

            ProjectPropertyElement propertyElement = propertyGroup.AddProperty(name, unevaluatedValue);

            if (setIfEmpty && condition != null)
            {
                propertyElement.Condition = $" '$({propertyElement.Name})' == '' And {condition} ";
            }
            else if (setIfEmpty)
            {
                propertyElement.Condition = $" '$({propertyElement.Name})' == '' ";
            }
            else
            {
                propertyElement.Condition = condition;
            }

            if (label != null)
            {
                propertyElement.Label = label;
            }

            return(this);
        }
            /// <summary>
            /// Creates a regular evaluated property, with backing XML.
            /// Called by Project.SetProperty.
            /// Property MAY NOT have reserved name and MAY NOT overwrite a global property.
            /// Predecessor is any immediately previous property that was overridden by this one during evaluation and may be null.
            /// </summary>
            internal ProjectPropertyXmlBackedWithPredecessor(Project project, ProjectPropertyElement xml, string evaluatedValueEscaped, ProjectProperty predecessor)
                : base(project, xml, evaluatedValueEscaped)
            {
                ErrorUtilities.VerifyThrowArgumentNull(predecessor, "predecessor");

                _predecessor = predecessor;
            }
        void IConfigurationOrPlatformNameCollection.Rename(string oldName, string newName)
        {
            newName = ValidateName(newName);
            if (newName == null)
            {
                throw new ArgumentException();
            }

            lock (project.SyncRoot) {
                foreach (ProjectPropertyGroupElement g in project.MSBuildProjectFile.PropertyGroups.Concat(project.MSBuildUserProjectFile.PropertyGroups))
                {
                    // Rename the default configuration setting
                    ProjectPropertyElement prop = FindConfigElement(g);
                    if (prop != null && ConfigurationAndPlatform.ConfigurationNameComparer.Equals(prop.Value, oldName))
                    {
                        prop.Value = newName;
                    }

                    // Rename the configuration in conditions
                    var gConfig = ConfigurationAndPlatform.FromCondition(g.Condition);
                    if (HasName(gConfig, oldName))
                    {
                        g.Condition = SetName(gConfig, newName).ToCondition();
                    }
                }
                project.LoadConfigurationPlatformNamesFromMSBuild();

                AdjustMapping(oldName, newName);
            }
        }
        public Task SetProjectGuidAsync(Guid value)
        {
            // Avoid searching for the <ProjectGuid/> if we've already checked previously in GetProjectGuidAsync.
            // This handles project open, avoids us needed to take another read-lock during setting of it.
            //
            // Technically a project could add a <ProjectGuid/> latter down the track by editing the project or
            // reloading from disk, however, both the solution, CPS and other components within Visual Studio
            // do not handle the GUID changing underneath them.
            if (_isPersistedInProject == false)
            {
                return(Task.CompletedTask);
            }

            return(_projectAccessor.OpenProjectXmlForUpgradeableReadAsync(_project, async(projectXml, cancellationToken) =>
            {
                ProjectPropertyElement property = FindProjectGuidProperty(projectXml);
                if (property != null)
                {
                    _isPersistedInProject = true;

                    // Avoid touching the project file unless the actual GUID has changed, regardless of format
                    if (!TryParseGuid(property, out Guid result) || value != result)
                    {
                        await _projectAccessor.OpenProjectXmlForWriteAsync(_project, (root) =>
                        {
                            property.Value = ProjectCollection.Escape(value.ToString("B").ToUpperInvariant());
                        }).ConfigureAwait(true);
                    }
                }
                else
                {
                    _isPersistedInProject = false;
                }
            }));
        }
 List <ProjectPropertyElement> FindSharpDevelopProjectProperties(ProjectPropertyElement msbuildProjectProperty)
 {
     return(sharpDevelopProject
            .MSBuildProjectFile
            .Properties
            .Where(property => String.Equals(property.Name, msbuildProjectProperty.Name, StringComparison.OrdinalIgnoreCase))
            .ToList());
 }
            /// <summary>
            /// Creates a regular evaluated property, with backing XML.
            /// Called by Project.SetProperty.
            /// Property MAY NOT have reserved name and MAY NOT overwrite a global property.
            /// Predecessor is any immediately previous property that was overridden by this one during evaluation and may be null.
            /// </summary>
            internal ProjectPropertyXmlBacked(Project project, ProjectPropertyElement xml, string evaluatedValueEscaped)
                : base(project, evaluatedValueEscaped)
            {
                ErrorUtilities.VerifyThrowArgumentNull(xml, "xml");
                ErrorUtilities.VerifyThrowInvalidOperation(!ProjectHasMatchingGlobalProperty(project, xml.Name), "OM_GlobalProperty", xml.Name);

                _xml = xml;
            }
 internal ProjectPropertyGroupTaskPropertyInstance(ProjectPropertyElement xml)
 {
     Condition         = xml.Condition;
     Name              = xml.Name;
     Value             = xml.Value;
     ConditionLocation = xml.ConditionLocation;
     Location          = xml.Location;
 }
Example #23
0
        private void AddPropertyToPropertyGroup(ProjectPropertyElement property, ProjectPropertyGroupElement destinationPropertyGroup)
        {
            var outputProperty = destinationPropertyGroup.ContainingProject.CreatePropertyElement("___TEMP___");

            outputProperty.CopyFrom(property);

            destinationPropertyGroup.AppendChild(outputProperty);
        }
Example #24
0
 public VSProjectProperty(VSProject project, ProjectPropertyElement internalProjectProperty)
 {
     this.Name       = (string)s_ProjectPropertyElement_Name.GetValue(internalProjectProperty, null) as string;
     this.Value      = (string)s_ProjectPropertyElement_Value.GetValue(internalProjectProperty, null) as string;
     this.XmlElement = (XmlElement)s_ProjectPropertyElement_XmlElement.GetValue(internalProjectProperty, null) as XmlElement;
     this.project    = project;
     this.internalProjectProperty = internalProjectProperty;
 }
Example #25
0
        public void ReadProperty()
        {
            ProjectPropertyElement property = GetPropertyXml();

            Assert.Equal("p", property.Name);
            Assert.Equal("v", property.Value);
            Assert.Equal("c", property.Condition);
        }
Example #26
0
        public void SetName()
        {
            ProjectPropertyElement property = GetPropertyXml();

            property.Name = "p2";
            Assert.Equal("p2", property.Name);
            Assert.Equal(true, property.ContainingProject.HasUnsavedChanges);
        }
Example #27
0
        internal ProjectPropertyElement ToProjectPropertyElement(ProjectElementContainer parent)
        {
            ProjectPropertyElement property = parent.ContainingProject.CreatePropertyElement(Name);

            property.Value = EvaluatedValue;
            parent.AppendChild(property);

            return(property);
        }
Example #28
0
 private void TracePropertyInfo(string message, ProjectPropertyElement mergedProperty)
 {
     MigrationTrace.Instance.WriteLine(String.Format(
                                           LocalizableStrings.PropertyInfo,
                                           nameof(PropertyTransformApplicator),
                                           message,
                                           mergedProperty.Name,
                                           mergedProperty.Value));
 }
Example #29
0
        private bool PropertiesAreEqual(ProjectPropertyElement nonConfigurationOutput, ProjectPropertyElement configurationOutput)
        {
            if (configurationOutput != null && nonConfigurationOutput != null)
            {
                return(string.Equals(nonConfigurationOutput.Value, configurationOutput.Value, StringComparison.Ordinal));
            }

            return(configurationOutput == nonConfigurationOutput);
        }
Example #30
0
        public void SetInvalidNullValue()
        {
            Assert.Throws <ArgumentNullException>(() =>
            {
                ProjectPropertyElement property = GetPropertyXml();

                property.Value = null;
            }
                                                  );
        }